一、selenium介绍

  • selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题
  • selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器

    1. from selenium import webdriver
    2. #webdriver可以认为是浏览器的驱动器,要驱动浏览器必须用到webdriver,支持多种浏览器
    3. browser=webdriver.Chrome()
    4. browser=webdriver.Firefox()
    5. browser=webdriver.PhantomJS()
    6. browser=webdriver.Safari()
    7. browser=webdriver.Edge()
  • 官网:http://selenium-python.readthedocs.io

  • Selenium是一个自动化测试工具,支持各种浏览器,包括Chrome、Safari、Firefox等主流界面式浏览器,也包括PhantomJS等无界面浏览器,通俗来说Selenium支持浏览器驱动,可以对浏览器进行控制。而且Selenium支持多种语言开发,比如Java、C、Ruby,还有Python,因此Python+Selenium+PhantomJS的组合就诞生了。PhantomJS负责渲染解析JavaScript, Selenium负责驱动浏览器和与Python对接,Python负责做后期处理,三者构成了一个完整的爬虫结构。

    二、环境搭建


1、有界面浏览器

  • 安装:selenium+chromedriver

    (1)安装selenium

  • **pip3 install selenium**

    (2)获取浏览器的驱动程序

  • 不获得浏览器的驱动程序则无法操作浏览器。以谷歌浏览器为例:谷歌浏览器驱动下载地址

  • 下载的驱动程序必须和浏览器的版本统一,具体查看selenium之chromedriver与chrome版本映射表

    (3)获取浏览器的驱动程序

    ① 查看浏览器版本

  • Chrome -> 关于Google Chrome

image.png

② 下载浏览器对应的驱动程序

  • 查看chrome和chromedriver的对应关系,我们应该下载 96.0.4664.45 版本对应的chromedriver驱动

image.png

  • 我选择Windows版本的驱动下载,解压后,将文件chromedriver拷贝python安装路径的scripts目录中即可

    (4)验证安装

    ```python from selenium import webdriver

driver=webdriver.Chrome() #弹出浏览器 driver.get(‘https://www.baidu.com‘) driver.page_source #selenium的page_source方法可以直接返回页面源码

  1. <a name="KMYGz"></a>
  2. ### (5)注意
  3. - selenium3默认支持的webdriver是Firfox,而Firefox需要安装geckodriver 下载链接:[https://github.com/mozilla/geckodriver/releases](https://github.com/mozilla/geckodriver/releases)
  4. <a name="IBxnY"></a>
  5. ## 2、无界面浏览器
  6. - selenium+phantomjs
  7. - PhantomJS不再更新
  8. <a name="tj38v"></a>
  9. ### (1)安装selenium
  10. - `**pip3 install selenium**`
  11. <a name="nUtgx"></a>
  12. ### (2)下载phantomJS
  13. - 下载phantomjs,解压后把phantomjs.exe所在的bin目录放到环境变量
  14. - 下载链接:[http://phantomjs.org/download.html](http://phantomjs.org/download.html)
  15. <a name="YV1IR"></a>
  16. ### (3)验证安装
  17. ```powershell
  18. #验证安装
  19. C:\Users\Administrator>phantomjs
  20. phantomjs> console.log('egon gaga')
  21. egon gaga
  22. undefined
  23. phantomjs> ^C
  24. C:\Users\Administrator>python3
  25. Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
  26. Type "help", "copyright", "credits" or "license" for more information.
  27. >>> from selenium import webdriver
  28. >>> driver=webdriver.PhantomJS() #无界面浏览器
  29. >>> driver.get('https://www.baidu.com')
  30. >>> driver.page_source

(3)selenium+谷歌浏览器headless模式

  • 在 PhantomJS 年久失修, 后继无人的节骨眼 ,Chrome 出来救场, 再次成为了反爬虫 Team 的噩梦
  • 自Google 发布 chrome 59 / 60 正式版 开始便支持Headless mode ,这意味着在无 GUI 环境下, PhantomJS 不再是唯一选择 ```python from selenium import webdriver from selenium.webdriver.chrome.options import Options

chrome_options = Options() # 实例化一个启动参数对象 chrome_options.add_argument(‘window-size=1920x3000’) # 指定浏览器分辨率 chrome_options.add_argument(‘—disable-gpu’) # 谷歌文档提到需要加上这个属性来规避bug chrome_options.add_argument(‘—hide-scrollbars’) # 隐藏滚动条, 应对一些特殊页面 chrome_options.add_argument(‘blink-settings=imagesEnabled=false’) # 不加载图片, 提升速度 chrome_options.add_argument(‘—headless’) # 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败 chrome_options.binary_location = r”C:\Program Files (x86)\Google\Chrome\Application\chrome.exe” # 手动指定使用的浏览器位置

driver = webdriver.Chrome(options=chrome_options) driver.get(‘https://www.baidu.com‘)

print(‘hao123’ in driver.page_source)

driver.close() # 切记关闭浏览器,回收资源

  1. <a name="uvRNe"></a>
  2. # 三、基本使用
  3. <a name="AdhSS"></a>
  4. ## 1、声明浏览器对象
  5. - 注意点一,Python文件名或者包名不要命名为selenium,会导致无法导入
  6. ```python
  7. from selenium import webdriver
  8. #webdriver可以认为是浏览器的驱动器,要驱动浏览器必须用到webdriver,支持多种浏览器,这里以Chrome为例
  9. browser = webdriver.Chrome()

2、访问页面并获取网页html

  1. from selenium import webdriver
  2. browser = webdriver.Chrome()
  3. browser.get('https://www.taobao.com')
  4. print(browser.page_source) # browser.page_source是获取网页网页的全部html
  5. browser.close()

3、输入内容到搜索框并查找

  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By
  3. from selenium.webdriver.common.keys import Keys
  4. import time
  5. driver = webdriver.Chrome() # 获取chrome浏览器的驱动
  6. driver.get("http://www.baidu.com") # 调用get方法,打开百度首页
  7. assert u"百度" in driver.title # 判断标题中是否包含百度字样
  8. elem = driver.find_element(By.NAME, "wd") # 通过元素名称wd获取输入框
  9. elem.clear()
  10. elem.send_keys(u"网络爬虫") # 通过send_keys方法将网络爬虫填写其中
  11. elem.send_keys(Keys.RETURN) # 回车
  12. time.sleep(3) # 延时3秒
  13. assert u"网络爬虫. " not in driver.page_source # 判断搜索页面是否有网络爬虫字样
  14. driver.close() # 最后关闭driver

四、元素选取

  • 要想对页面进行操作,首先要做的是选中页面元素

image.png

  • 除了上面具有确定功能的方法,还有两个通用方法**find_element****find_elements**,可以通过传入参数来指定功能。示例如下

    1. from selenium.webdriver.common.by import By
    2. driver.find_element(By.XPATH, '//button[text()="Some text"]')
  • 这一个例子是通过xpath表达式来查找,方法中第一个参数是指定选取元素的方式,第二个参数是选取元素需要传入的值或表达式。第一个参数还可以传入By类中的以下值:

    1. By.ID
    2. By.XPATH
    3. By.LINK_TEXT
    4. By.PARTIAL_LINK_TEXT
    5. By.NAME
    6. By.TAG_NAME
    7. By.CLASS_NAME
    8. By.CSS_SELECTOR
  • 下面通过一个HTML文档来讲解一下如何使用以上方法提取内容,HTML文档如下:

    1. <html>
    2. <body>
    3. <h1>Welcome</h1>
    4. <p class="content">用户登录</p>
    5. <form id="loginForm">
    6. <input name="username" type="text" />
    7. <input name="password" type="password" />
    8. <input name="continue" type="submit" value="Login" />
    9. <input name="continue" type="button" value="Clear" />
    10. </form>
    11. <a href="register.html">Register</a>
    12. </body>
    13. </html>
  • 定位方法的使用如表所示

image.png

五、页面操作

  • 以如下HTML文档为例介绍页面操作,login.html代码如下:

    1. <html>
    2. <head>
    3. <meta http-equiv="content-type" content="text/html; charset=gbk">
    4. </head>
    5. <body>
    6. <h1>Welcome</h1>
    7. <p class="content">用户登录</p>
    8. <form id="loginForm">
    9. <select name="loginways">
    10. <option value="email">邮箱</option>
    11. <option value="mobile">手机号</option>
    12. <option value="name">用户名</option>
    13. </select>
    14. <br/>
    15. <input name="username" type="text" /><br/>
    16. 密码
    17. <br/>
    18. <input name="password" type="password" />
    19. <br/><br/>
    20. <input name="continue" type="submit" value="Login" />
    21. <input name="continue" type="button" value="Clear" />
    22. </form>
    23. <a href="register.html">Register</a>
    24. </body>
    25. </html>
  • 效果如图所示

image.png

1、页面交互与填充表单

  • 第一步:初始化Chrome驱动,打开html文件,由于是本地文件,可以使用下面方式打开。

    1. driver = webdriver.chrome()
    2. driver.get("file:// /D:/phpstudy_pro/WWW/login.html")
  • 第二步:获取用户名和密码的输入框,和登录按钮。

    1. username = driver.find_element(By.NAME, 'username')
    2. password = driver.find_element(By.XPATH, ".//*[@id='loginForm']/input[2]")
    3. login_button = driver.find_element(By.XPATH, "//input[@type='submit']")
  • 第三步:使用send_keys方法输入用户名和密码,使用click方法模拟点击登录。

    1. username.send_keys("qiye")
    2. password.send_keys("qiye_pass")
    3. login.button.click()
  • 如果想清除username和password输入框的内容,可以使用clear方法

    1. username.clear()
    2. password.clear()
  • 上面还有一个问题没解决,如何操作下拉选项卡选择登录方式呢?第一种方法代码如下:

    1. select = driver.find_element(By.XPATH, "//form/select")
    2. all_options = select.find_elements(By.TAG_NAME, "option")
    3. for option in all_options:
    4. print("Value is: %s" % option.get_attribute("value"))
    5. option.click()
  • 在代码中首先获取select元素,也就是下拉选项卡。然后轮流设置了select选项卡中的每一个option选项。这并不是一个非常好的办法。官方提供了更好的实现方式,在WebDriver中提供了一个叫Select方法,也就是第二种操作方式。代码如下:

    1. from selenium.webdriver.support.ui import Select
    2. select = Select(driver.find_element(By.XPATH, '//form/select'))
    3. select.select_by_index(index)
    4. select.select_by_visible_text("text")
    5. select.select_by_value(value)
  • 它可以根据索引、文字、value值来选择选项卡中的某一项。如果select标记中multiple=”multiple”,也就是说这个select标记支持多选,Select对象提供了支持此功能的方法和属性。示例如下:

    1. 取消所有的选项:select.deselect_all()
    2. 获取所有的选项:select.options
    3. 获取已选中的选项:select. all_selected_options

    2、元素拖拽

  • 元素的拖拽即将一个元素拖到另一个元素的位置,类似于拼图。首先要找到源元素和目的元素,然后使用ActionChains类可以实现。代码如下: ```python element = driver.find_element(By.NAME, “source”) target = driver.find_element(By.NAME, “target”)

from selenium.webdriver import ActionChains action_chains = ActionChains(driver) action_chains.drag_and_drop(element, target).platform()

  1. <a name="QT4Yj"></a>
  2. ## 3、窗口和页面frame的切换
  3. - 一个浏览器一般都会开多个窗口,我们可以switch_to_window方法实现指定窗口的切换。示例如下:
  4. ```python
  5. driver.switch_to_window("windowName")
  • 也可以通过window handle来获取每个窗口的操作对象。示例如下:

    1. for handle in driver.window_handles:
    2. driver.switch_to_window(handle)
  • 如需切换页面frame,可以使用switch_to_frame方法,示例如下:

    1. iframe = browser.find_element(By.ID, "loginIframe")
    2. browser.switch_to.frame(iframe)

    4、弹窗处理

  • 如果你在处理页面的过程中,触发了某个事件,跳出弹框。可以使用switch_to_alert获取弹框对象,从而进行关闭弹框、获取弹框信息等操作。示例如下:

    1. alert = driver.switch_to_alert()
    2. alert.dismiss()

    5、历史记录

  • 操作页面的前进和后退功能,示例如下

    1. driver.forward()
    2. driver.back()

    6、Cookie处理

  • 可以使用get_cookies方法获取cookie,也可以使用add_cookie方法添加cookie信息。示例如下: ```python from selenium import webdriver

driver = webdriver.Chrome() driver.get(“http://www.baidu.com“) cookie = {‘name’: ‘foo’, ‘value’: ‘bar’} driver.add_cookie(cookie) driver.get_cookies()

  1. <a name="iS6C0"></a>
  2. ## 7、设置phantomJS请求头中User-Agent
  3. - 这个功能在爬虫中非常有用,一般针对phantomJS的反爬虫措施都会检测这个字段,默认的User-Agent中含有phantomJS内容,可以通过代码进行修改。代码如下:
  4. ```python
  5. from selenium import webdriver
  6. dcap = dict(DesiredCapabilities.PHANTOMJS)
  7. dcap["phantomjs.page.settings.userAgent"] = (
  8. "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) "
  9. "Chrome/48.0.2564.23 Mobile Safari/537.36 "
  10. )
  11. driver = webdriver.PhantomJS() # desired_capabilities=dcap)
  12. driver.get("http://www.google.com")
  13. driver.quit()

六、等待

  • 由于现在很多网站采用Ajax技术,不确定网页元素什么时候能被完全加载,所以网页元素的选取会比较困难,这时候就需要等待。Selenium有两种等待方式,一种是显式等待,一种是隐式等待。

    1、显式等待

  • 显式等待是一种条件触发式的等待方式,指定某一条件直到这个条件成立时才会继续执行,可以设置超时时间,如果超过这个时间元素依然没被加载,就会抛出异常。示例如下: ```python from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome() driver.get(“https://somedoamin/url_that_delays_loading“) try: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, “myDynamicElement”)) ) finally: driver.quit()

  1. - 以上代码加载[http://somedomain/url_that_delays_loading](http://somedomain/url_that_delays_loading)页面,并定位id为myDynamic Element的元素,设置超时时间为10s。WebDriverWait默认会500ms检测一下元素是否存在。
  2. - Selenium提供了一些内置的用于显式等待的方法,位于expected_conditions类中,方法名称如表所示:
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2893488/1637159967467-bff61ce7-362f-4bcc-99bd-493e56f573f5.png#clientId=ua0c2916f-1062-4&from=paste&id=u08be6ffc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=797&originWidth=1189&originalType=binary&ratio=1&size=421432&status=done&style=none&taskId=u12a43499-7a55-4bd2-84ed-0d9f2b22122)
  4. <a name="NnFZW"></a>
  5. ## 2、隐式等待
  6. - 隐式等待是在尝试发现某个元素的时候,如果没能立刻发现,就等待固定长度的时间,类似于socket超时,默认设置是0秒。一旦设置了隐式等待时间,它的作用范围是Webdriver对象实例的整个生命周期,也就是说Webdriver执行每条命令的超时时间都是如此。如果大家感觉设置的时间过长,可以进行不断地修改。使用方法示例如下:
  7. ```python
  8. from selenium import webdriver
  9. driver = webdriver.Chrome()
  10. driver.implicitly_wait(10) # seconds
  11. driver.get("http://somedomain/url_that_delays_loading")
  12. myDynamicElement = driver.find_element(By.ID, "myDynamicElement")

3、线程休眠

  • time.sleep(time),这是使用线程休眠延时的办法,也是比较常用的。

    七、动态爬虫:爬取去哪儿网

  • 讲解完了Selenium,接下来编写一个爬取去哪网酒店信息的简单动态爬虫。目标是爬取上海今天的酒店信息,并将这些信息存成文本文件。下面将整个目标进行功能分解:

1)搜索功能,在搜索框输出地点和入住时间,点击搜索按钮
2)获取一页完整的数据。由于去哪网一个页面数据分为两次加载,第一次加载15条数据,这时候需要将页面拉到底部,完成第二次数据加载。
3)获取一页完整且渲染过的HTML文档后,使用BeautifulSoup将其中的酒店信息提取出来进行存储。
4)解析完成,点击下一页,继续抽取数据。

  • 第一步:找到酒店信息的搜索页面,如图所示。

image.png

  • 使用Firebug查看Html结果,可以通过selenium获取目的地框、入住日期、离店日期和搜索按钮的元素位置,输入内容,并点击搜索按钮。

    1. ele_toCity = driver.find_element(By.NAME, 'toCity')
    2. ele_fromDate = driver.find_element(By.ID, 'fromDate')
    3. ele_toDate = driver.find_element(By.ID, 'toDate')
    4. ele_search = driver.find_element(By.CLASS_NAME, 'search-btn')
    5. ele_toCity.clear()
    6. ele_toCity.send_keys(to_city)
    7. ele_toCity.click()
    8. ele_fromDate.clear()
    9. ele_toCity.send_keys(fromdate)
    10. ele_toDate.clear()
    11. ele_toDate.send_keys(todate)
    12. ele_search.click()
  • 第二步:分两次获取一页完整的数据,第二次让driver执行js脚本,把网页拉到底部。 ```python try: WebDriverWait(driver, 10).until(

    1. EC.title_contains(unicode(to_city))

    ) except Expection, e: print(e) break time.sleep(5)

js = “window.scrollTo(0, document.body.scrollHeight);” driver.execute_script(js) time.sleep(5) htm_const = driver.page_source

  1. - **第三步:**使用BeautifulSoup解析酒店信息,并将数据进行清洗和存储。
  2. ```python
  3. soup = BeautifulSoup(htm_const, 'html.parser', from_encoding='utf-8')
  4. infos = soup.find_all(class="item_hotel_info")
  5. f = codecs.open(unicode(to_city)+unicode(fromdate)+u'.html', 'a', 'utf-8')
  6. for info in infos:
  7. f.write(str(page_num)+'--'*50)
  8. content = info.get_text().replace(" ", "").replace("\t", "").strip()
  9. for line in [ln for ln in content.splitlines() if ln.strip()]:
  10. f.write(line)
  11. f.write('\r\n')
  12. f.close()
  • 第四步:点击下一页,继续重复这一个过程。

    1. next_page = WebDriverWait(driver, 10).until(
    2. EC.visibility_of(driver.find_element(By.CSS_SELECTOR, ".item.text"))
    3. )
    4. next_page.click()
  • 这个小例子只是简单实现了功能,完整代码如下: ```python class QunaSpider(object): def get_hotel(self, driver, to_city, fromdate, todate):

    1. ele_toCity = driver.find_element(By.NAME, 'toCity')
    2. ele_fromDate = driver.find_element(By.ID, 'fromDate')
    3. ele_toDate = driver.find_element(By.ID, 'toDate')
    4. ele_search = driver.find_element(By.CLASS_NAME, 'search-btn')
    5. ele_toCity.clear()
    6. ele_toCity.send_keys(to_city)
    7. ele_toCity.click()
    8. ele_fromDate.clear()
    9. ele_toCity.send_keys(fromdate)
    10. ele_toDate.clear()
    11. ele_toDate.send_keys(todate)
    12. ele_search.click()
    13. page_num = 0
    14. while True:
    15. try:
    16. WebDriverWait(driver, 10).until(
    17. EC.title_contains(unicode(to_city))
    18. )
    19. except Exception as e:
    20. print(e)
    21. break
    22. time.sleep(5)
    23. js = "window.scrollTo(0, document.body.scrollHeight);"
    24. driver.execute_script(js)
    25. time.sleep(5)
    26. htm_const = driver.page_source
    27. soup = BeautifulSoup(htm_const, 'html.parser', from_encoding='utf-8')
    28. infos = soup.find_all(class_="item_hotel_info")
    29. f = codecs.open(unicode(to_city) + unicode(fromdate) + u'.html', 'a', 'utf-8')
    30. for info in infos:
    31. f.write(str(page_num) + '--' * 50)
    32. content = info.get_text().replace(" ", "").replace("\t", "").strip()
    33. for line in [ln for ln in content.splitlines() if ln.strip()]:
    34. f.write(line)
    35. f.write('\r\n')
    36. f.close()
    37. try:
    38. next_page = WebDriverWait(driver, 10).until(
    39. EC.visibility_of(driver.find_element(By.CSS_SELECTOR, ".item.text"))
    40. )
    41. next_page.click()
    42. page_num += 1
    43. time.sleep(10)
    44. except Exception as e:
    45. print(e)
    46. break

    def crawl(self, root_url, to_city):

    1. today = datatime.date.today().strftime('%Y-%m-%d')
    2. tomorrow = datetime.date.today() + datetime.timedelta(days=1)
    3. tomorrow = tomorrow.strftime('%Y-%m-%d')
    4. driver = webdriver.Chrome(executable_path='D:\geckodriver_win32\gecko-driver.exe')
    5. driver.set_page_load_ti meout(50)
    6. driver.get(root_url)
    7. driver.maximize_window() # 将浏览器最大化显示
    8. driver.implicitly_wait(10) # 控制间隔时间,等待浏览器反映
    9. self.get_hotel(driver, to_city, today, tomorrow)

if name == “main“: spider = QunaSpider() spider.crawl(‘http://hotel.qunar.com/‘, u”上海”) ```

八、小结

  • 本章讲解了两种动态网站抓取的方法,两种方法有利有弊。直接从JavaScript中提取数据远比使用Selenium+PhantomJS速度快,占用系统内存小,但是碰到参数加密的情况,分析起来就较为复杂,而Selenium+PhantomJS恰恰避免了这个问题,反爬虫能力很强,基本上可以躲过大部分的检测。两种方法都要掌握,针对不同的网站使用不同的策略。