使用selenium爬虫抓取数据


总体介绍

  • Selenium 简单入门教程
  • 使用 Selenium 模拟访问网易数据中心的房价数据
  • 使用 Pandas 处理数据
  • 使用 Matplotlib 绘制图表

Selenium

安装

  • conda install selenium 或 pip install selenium
  • 下载浏览器驱动。在 selenium 的 download 页面,Third Party Drivers 处下载对应浏览器驱动,或使用下方链接
  • Linux 和 MacOS 用户下载好之后, 将下载好的”geckodriver”文件放在你的计算机的 “/usr/bin” 或 “/usr/local/bin” 目录。并赋予执行权限,如下所示:
sudo cp geckodriver /usr/local/bin
sudo chmod +x /usr/local/bin/geckodriver

windows用户应该是把geckodriver文件放在环境变量中(PATH),详见百度经验
或者在代码中引用文件所在位置,如下:

 from selenium import webdriver
  path="D:\\chromedriver.exe" #替换成geckodriver实际所在目录
  driver=webdriver.Chrome(path)
  driver.get("http://www.yahoo.com")
  driver.close()
  driver.quit()

安装火狐浏览器插件Katalon Recorder

该组件用于录制用户在浏览器中的操作,并生成python等代码,省去了自己写代码的麻烦。类似按键精灵,可以参考莫烦的视频教程。

  • 工具>附加组件>搜索 Katalon Recorder >安装
  • 在需要录制的页面打开 Katalon Recorder ,点击录制,然后完成你想录制的操作,点击停止录制,导出代码。例如我在百度搜索框中输入“P2P”,然后点击搜索,录制得到的代码如下:
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re

class UntitledTestCase(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.driver.implicitly_wait(30)
        self.base_url = "https://www.katalon.com/"
        self.verificationErrors = []
        self.accept_next_alert = True
    
    def test_untitled_test_case(self):
        driver = self.driver
        driver.get("https://www.baidu.com/")
        driver.find_element_by_id("kw").click()
        driver.find_element_by_id("kw").clear()
        driver.find_element_by_id("kw").send_keys("P2P")
        driver.find_element_by_id("su").click()
    
    def is_element_present(self, how, what):
        try: self.driver.find_element(by=how, value=what)
        except NoSuchElementException as e: return False
        return True
    
    def is_alert_present(self):
        try: self.driver.switch_to_alert()
        except NoAlertPresentException as e: return False
        return True
    
    def close_alert_and_get_its_text(self):
        try:
            alert = self.driver.switch_to_alert()
            alert_text = alert.text
            if self.accept_next_alert:
                alert.accept()
            else:
                alert.dismiss()
            return alert_text
        finally: self.accept_next_alert = True
    
    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)

if __name__ == "__main__":
    unittest.main()

其中,以下内容是与我们的操作相关的代码

def test_untitled_test_case(self):
        driver = self.driver
        driver.get("https://www.baidu.com/")              #打开百度搜索页面
        driver.find_element_by_id("kw").click()          #点击搜索输入框,这个在写代码时可以不要
        driver.find_element_by_id("kw").clear()         #清空里面已有的输入
        driver.find_element_by_id("kw").send_keys("P2P")    #在里面输入P2P搜索词
        driver.find_element_by_id("su").click()          #点击搜索按钮

后台运行

  • 安装PhantomJs,见参考链接
  • driver = webdriver.PhantomJS()

简单教程

  • 连接浏览器,以火狐Firefox浏览器为例
from selenium import webdriver
self.driver = webdriver.Firefox()
  • 打开某个页面
driver.get("https://www.baidu.com/")
  • 等待具有某个ID的元素加载完成,这用来保证页面以及加载出来
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0
from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "kw"))) #这是百度输入框的ID

获取输入框ID可以使用Firefox或Chrome浏览器的提供的调试工具,对于Firefox浏览器,在页面右键选择“查看元素”即可打开调试页面,点击调试页面左上角的“选择一个元素”按钮,再点击百度的输入框,可以在下方看到它的ID为“kw”

  • 使用前面我们录制得到的代码实现自动输入搜索词
driver.find_element_by_id("kw").clear()         #清空里面已有的输入
driver.find_element_by_id("kw").send_keys("P2P")    #在里面输入P2P搜索词
driver.find_element_by_id("su").click()          #点击搜索按钮
  • 通过“审查元素”分析,我们发现百度搜索得到的每个词条的class属性都为result c-container,通过这个,我们可以得到所有搜索结果
e_item = driver.find_elements_by_xpath('//div[@class="result c-container "]')
print('\n'.join([e.find_element_by_tag_name('a').text for e in e_item])) #打印每个条目的内容

结果如下:

【网贷之家】中国首家权威p2p网贷投资理财行业门户_网贷理财_p2p…
P2P平台被清盘后,你会怎样捍卫自身利益?
P2P金融_百度百科
P2psearcher绿色版下载_P2psearcher免安装版官方下载-华军软件园
国家出手,P2P现回暖迹象!
迪蒙–供应链金融、黄金理财、融资租赁、消费金融系统专业开发商

同样,我们也可以获得每个条目的链接,进入链接即可抓取我们想要的数据

print('\n'.join([e.find_element_by_tag_name('a').get_attribute('href') for e in e_item]))

结果如下:

http://www.baidu.com/link?url=80_XPZ-NHHGGwNmIpG5kNuAn0b8MKU5rHrdy-dJXwvy
http://www.baidu.com/link?url=Vik9gXKc-hz-tGHQ1JcsRVzCn7vXRMr_HXNuuYa_sc_4o_Epq6Z7v7rZeW070Orxr3rWtAnAz44FR9vZSDtzcykS7WzmdI_JGfml2jERU
http://www.baidu.com/link?url=sq7r9PEl1YjbGpDFNQKauiE21G7wKHYK9PpL1gRG6j6OPEpPrDXmTVbaO0ExdCoevQVaZzjqbvm0Pe_c1p_DHigIukY5n_-yr784N6ulcQC
http://www.baidu.com/link?url=tXD8qkEJClA6bqXKjIq0Zg9gICFvKaC6QgCpE1FGn1xnXXd_8UAfGrNRLvH1cX-PMdkJ15mhnMUJzld1R_-vc_
http://www.baidu.com/link?url=Jd1r4dPGnyYHxC9VuhtzWQFV3-Je4WzLC0UZJqdMJQT1ExBYOuBDI5mpRKDIT5lV564GTSe1Xw_3ZRqxjVI6UDU0SKh-spSslgiLGgsie0C
http://www.baidu.com/link?url=ONtuKqNGH5M2PCqcEJ9AuQMLfWpoq8KTVMmY9mKLjya

  • 抓完一页的结果之后,再翻到下一页
driver.find_element_by_xpath(u"//div[@id='page']/descendant::span[text()='2']").click() #其中2为页码,可以依次换成其他页码
  • 完整代码如下:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0
from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0
driver = webdriver.Firefox()
driver.get("https://www.baidu.com")
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "kw")))
driver.find_element_by_id("kw").clear()         #清空里面已有的输入
driver.find_element_by_id("kw").send_keys("P2P")    #在里面输入P2P搜索词
driver.find_element_by_id("su").click()          #点击搜索按钮

for i in range(2,5,1):
    time.sleep(1)
    e_item = driver.find_elements_by_xpath('//div[@class="result c-container "]')
    print('\n'.join([e.find_element_by_tag_name('a').text for e in e_item]))
    driver.find_element_by_xpath(f"//div[@id='page']/descendant::span[text()='{i}']").click()
        
driver.quit()

结果如下:

【网贷之家】中国首家权威p2p网贷投资理财行业门户_网贷理财_p2p…
P2P金融_百度百科
P2psearcher绿色版下载_P2psearcher免安装版官方下载-华军软件园
P2P | 异次元软件世界
迪蒙–供应链金融、黄金理财、融资租赁、消费金融系统专业开发商
P2P“大崩盘”:上百家平台爆雷 众多投资人卷入其中|P2P…_新浪科技
一周40家P2P“爆雷”,一旦踩雷就血本无归? | 小巴问大…_搜狐财经
p2p有哪些公司_网贷天眼
P2P“大崩盘”:上百家平台爆雷 众多投资人卷入其中|P2P…_新浪科技
世上再无P2P-虎嗅网
网贷天眼-你的互联网金融理财助手_P2P网贷理财
互联网p2p理财网贷平台_P2P投资理财专家_现金万家P2P投资理财网
半个月超40家平台"爆雷" P2P行业发生了什么事_网易科技
【你我贷官网】平稳运营7年有余的P2P网络借贷信息中介平台,网上…
一天17个P2P平台出问题?起码一半存在
p2p什么时候进入中国的_网贷天眼
红岭创投_值得信赖的P2P网贷平台_P2P个人理财平台
红岭创投_值得信赖的P2P网贷平台_P2P个人理财平台
对等网络_百度百科
P2P 是什么? - 知乎
频频爆雷背后暴露三大原因 P2P网贷行业该走向何方?互联网金融
P2P理财_P2P理财排名_P2P网贷平台 - 融360
P2P网贷|P2P理财|P2P贷款 - 专业的P2P网贷产品导购平台_希…_希财
富金利_P2P平台_专业安全的P2P网贷投融资平台【唯一官网】
P2P - 金评媒
p2p吧-百度贴吧
P2P | 人人都是产品经理

抓取网易房地产数据例子

# -*- coding: utf-8 -*-
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0
from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0
from selenium.webdriver.support import expected_conditions
import unittest, time, re
import pandas as pd

class MyCrawler(object):
    def __init__(self):
        self.path = "/home/liucc/Workspace/Housing/data"
        
        if not os.path.exists(self.path):
            os.mkdir(self.path)
            
        self.driver = webdriver.Firefox()
        self.base_url = "http://data.house.163.com/bj/housing/trend/district/todayprice/{date:s}/{interval:s}/allDistrict/1.html?districtname={disname:s}#stoppoint"
        self.data = None 
    
    def craw_page(self,date="2014.01.01-2018.09.15",interval="month",disname="全市"):
        driver = self.driver
        url = self.base_url.format(date=date,interval=interval,disname=disname)
        print('访问网址:'+url)
        driver.get(url)
        
        try:
            # we have to wait for the page to refresh, the last thing that seems to be updated is the title
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "resultdiv_1")))

            print(driver.title)
            
            self.data = pd.DataFrame()
            
            ct = True
            while ct:
                self.get_items_in_page(driver)
                
                e_pages = driver.find_elements_by_xpath('//div[@class="pager_box"]/a[@class="pager_b current"]/following::a[@class="pager_b "]')

                if len(e_pages)>0:
                    next_page_num = e_pages[0].text
                    e_pages[0].click()
                    
                    #通过判断当前页是否为我们点击页面的方式来等待页面加载完成
                    WebDriverWait(driver, 10).until(
                        expected_conditions.text_to_be_present_in_element(
                            (By.XPATH, '//a[@class="pager_b current"]'),
                            next_page_num
                        )
                    )
 
                else:
                    ct = False
                    break
            
            return self.data
            
        finally:
            driver.quit()
            
           
        
    def get_items_in_page(self,driver):
        e_tr = driver.find_elements_by_xpath("//tr[normalize-space(@class)='mBg1' or normalize-space(@class)='mBg2']")
        temp = pd.DataFrame(e_tr,columns=['web'])
        temp['时间']=temp.web.apply(lambda x:x.find_element_by_class_name('wd2').text.split(' ')[0])
        temp['套数']=temp.web.apply(lambda x:x.find_element_by_class_name('wd5').text)
        temp['均价']=temp.web.apply(lambda x:x.find_element_by_class_name('wd7').text)
        temp['去化']=temp.web.apply(lambda x:x.find_element_by_class_name('wd14').text)
        del temp['web']
        
        self.data = pd.concat([temp,self.data],axis=0)
  

mcraw = MyCrawler()
data = mcraw.craw_page()
访问网址:http://data.house.163.com/bj/housing/trend/district/todayprice/2014.01.01-2018.09.15/month/allDistrict/1.html?districtname=全市#stoppoint
北京全市-住宅-成交均价住宅成交数据_北京房价_网易北京房产
data= data.sort_values(by='时间')
print(data.to_string(index=False))
时间    套数      均价     去化
2014-01  4040  28,012  40.8%
2014-02  1302  26,990  40.5%
2014-03  4939  28,330  40.3%
2014-04  3388  26,372  39.4%
2014-05  3499  26,592  38.8%
2014-06  2780  28,627  59.8%
2014-07  3969  25,539  59.7%
2014-08  5221  24,814  59.6%
2014-09  3563  26,199  59.5%
2014-10  6554  23,045  59.7%
2014-11  8133  24,122  60.0%
2014-12  9337  25,287  59.8%
2015-01  7614  28,280  60.2%
2015-02  2750  23,755  60.3%
2015-03  2895  26,832  60.4%
2015-04  5341  25,460  60.5%
2015-05  5347  27,486  60.8%
2015-06  5634  30,185  60.9%
2015-07  9915  27,662  61.0%
2015-08  7475  28,371  61.4%
2015-09  5698  29,887  61.6%
2015-10  6205  26,145  61.8%
2015-11  4944  32,372  61.9%
2015-12  9451  29,368  62.3%
2016-01  4030  31,019  62.5%
2016-02  1886  33,160  62.6%
2016-03  4209  37,005  62.9%
2016-04  5245  36,337  63.2%
2016-05  4565  37,107  63.4%
2016-06  4400  37,236  63.4%
2016-07  6559  34,711  63.5%
2016-08  6895  32,322  63.6%
2016-09  4865  40,565  63.8%
2016-10  3747  40,285  64.3%
2016-11  2092  44,213  64.4%
2016-12  4210  41,741  64.5%
2017-01  2432  41,857  64.6%
2017-02  2308  42,876  64.6%
2017-03  2491  42,795  64.7%
2017-04  2471  45,886  64.8%
2017-05  2666  44,751  64.9%
2017-06  1812  48,792  64.9%
2017-07  1895  51,526  64.9%
2017-08  1577  47,958  64.9%
2017-09  1270  56,663  64.9%
2017-10  1340  55,968  64.8%
2017-11  2625  46,608  64.8%
2017-12  2748  53,320  64.8%
2018-01  1615  42,206  64.7%
2018-02  1138  42,280  64.7%
2018-03  1243  42,498  64.6%
2018-04  1408  46,897  64.6%
2018-05  3560  43,199  64.8%
2018-06  1884  43,391  64.9%
2018-07  2090  47,960  64.9%
2018-08  4181  41,096  65.0%
2018-09  2369  31,965  65.1%
data.to_csv('./data/housing_beijing.csv',index=False)
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 指定默认字体
plt.rcParams['font.sans-serif']=['SimHei'] 
plt.rcParams['font.family']='sans-serif' 
# 用来正常显示负号
plt.rcParams['axes.unicode_minus']=False

data = pd.read_csv('./data/housing_beijing.csv')
y = [float(s.replace(',','')) for s in data['均价'].values]
plt.plot(y,color='red', marker='o', linestyle='solid')
plt.xlabel(u'日期')
plt.ylabel(u'均价(元)')
plt.title(u'北京房价走势')
plt.show()

房价走势图

matplotlib中文乱码的解决详见参考链接


参考链接


版权声明:本文为liucc09原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。