最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

scrapy結合selenium解析動態(tài)頁面的實現(xiàn)

 更新時間:2020年09月28日 10:04:54   作者:NBody編程那些事  
這篇文章主要介紹了scrapy結合selenium解析動態(tài)頁面的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

 1. 問題

雖然scrapy能夠完美且快速的抓取靜態(tài)頁面,但是在現(xiàn)實中,目前絕大多數(shù)網站的頁面都是動態(tài)頁面,動態(tài)頁面中的部分內容是瀏覽器運行頁面中的JavaScript腳本動態(tài)生成的,爬取相對困難;

比如你信心滿滿的寫好了一個爬蟲,寫好了目標內容的選擇器,一跑起來發(fā)現(xiàn)根本找不到這個元素,當時肯定一萬個黑人問號

于是你在瀏覽器里打開F12,一頓操作,發(fā)現(xiàn)原來這你妹的是ajax加載的,不然就是硬編碼在js代碼里的,blabla的…

然后你得去調ajax的接口,然后解析json啊,轉成python字典啊,然后才能拿到你想要的東西

妹的就不能對我們這些小爬爬友好一點嗎?

于是大家伙肯定想過,“為啥不能瀏覽器看到是咋樣的html頁面,我們爬蟲得到的也是同樣的html頁面呢? 要是可以,那得多么美滋滋啊”

2. 解決方案

既然是想要得到和瀏覽器一模一樣的html頁面,那我們就先用瀏覽器渲染一波目標網頁,然后再將瀏覽器渲染后的html拿給scrapy進行進一步解析不就好了嗎

2.1 獲取瀏覽器渲染后的html

有了思路,肯定是網上搜一波然后開干啊,搜python操作瀏覽器的庫啊

貨比三家之后,找到了selenium這貨

selenium可以模擬真實瀏覽器,自動化測試工具,支持多種瀏覽器,爬蟲中主要用來解決JavaScript渲染問題。

臥槽,這就是我們要的東西啦

先試一波看看效果如何,目標網址http://quotes.toscrape.com/js/

別著急,先來看一下網頁源碼


我們想要的div.quote被硬編碼在js代碼中

用selenium試一下看能不能獲取到瀏覽器渲染后的html

from selenium import webdriver

# 控制火狐瀏覽器
browser = webdriver.Firefox()

# 訪問我們的目標網址
browser.get("http://quotes.toscrape.com/js/")

# 獲取渲染后的html頁面
html = browser.page_source

perfect,到這里我們已經順利拿到瀏覽器渲染后的html了,selenium大法好啊?

2.2 通過下載器中間件返回渲染過后html的Response

這里先放一張scrapy的流程圖

在這里插入圖片描述

所以我們只需要在scrapy下載網頁(downloader下載好網頁,構造Response返回)之前,通過下載器中間件返回我們自己<通過渲染后html構造的Response>不就可以了嗎?

道理我都懂,關鍵是在哪一步使用瀏覽器呢?

分析:

(1)我們的scrapy可能是有很多個爬蟲的,有些爬蟲處理的是純純的靜態(tài)頁面,而有些是處理的純純的動態(tài)頁面,又有些是動靜態(tài)結合的頁面(有可能列表頁是靜態(tài)的,正文頁是動態(tài)的),如果把<瀏覽器調用代碼>放在下載器中間件中,那么除非特別區(qū)分哪些爬蟲需要selenium,否則每一個爬蟲都用selenium去下載解析頁面的話,實在是太浪費資源了,就相當于殺雞用牛刀了,所以得出結論,<瀏覽器調用代碼>應該是放置于Spider類中更好一點;

(2)如果放置于Spider類中,就意味著一個爬蟲占用一個瀏覽器的一個tab頁,如果這個爬蟲里的某些Request需要selenium,而某些不需要呢? 所以我們還要在區(qū)分一下Request;

結論:

SeleniumDownloaderMiddleware(selenium專用下載器中間件):負責返回瀏覽器渲染后的ResponseSeleniumSpider(selenium專用Spider):一個spider開一個瀏覽器SeleniumRequest:只是繼承一下scrapy.Request,然后pass,好區(qū)分哪些Request需要啟用selenium進行解析頁面,相當于改個名

3. 擼代碼,盤他

 3.1 自定義Request

#!usr/bin/env python 
# -*- coding:utf-8 _*-
""" 
@author:Joshua
@description:
  只是繼承一下scrapy.Request,然后pass,好區(qū)分哪些Request需要啟用selenium進行解析頁面,相當于改個名
"""
import scrapy

class SeleniumRequest(scrapy.Request):
  """
  selenium專用Request類
  """
  pass

3.2 自定義Spider

#!usr/bin/env python 
# -*- coding:utf-8 _*-
""" 
@author:Joshua 
@description:
  一個spider開一個瀏覽器
"""
import logging
import scrapy
from selenium import webdriver


class SeleniumSpider(scrapy.Spider):
  """
  Selenium專用spider

  一個spider開一個瀏覽器

  瀏覽器驅動下載地址:http://www.cnblogs.com/qiezizi/p/8632058.html
  """
  # 瀏覽器是否設置無頭模式,僅測試時可以為False
  SetHeadless = True

  # 是否允許瀏覽器使用cookies
  EnableBrowserCookies = True

  def __init__(self, *args, **kwargs):
    super(SeleniumSpider, self).__init__(*args, **kwargs)
    
    # 獲取瀏覽器操控權
    self.browser = self._get_browser()

  def _get_browser(self):
    """
    返回瀏覽器實例
    """
    # 設置selenium與urllib3的logger的日志等級為ERROR
    # 如果不加這一步,運行爬蟲過程中將會產生一大堆無用輸出
    logging.getLogger('selenium').setLevel('ERROR')
    logging.getLogger('urllib3').setLevel('ERROR')
    
    # selenium已經放棄了PhantomJS,開始支持firefox與chrome的無頭模式
    return self._use_firefox()

  def _use_firefox(self):
    """
    使用selenium操作火狐瀏覽器
    """
    profile = webdriver.FirefoxProfile()
    options = webdriver.FirefoxOptions()
    
    # 下面一系列禁用操作是為了減少selenium的資源耗用,加速scrapy
    
    # 禁用圖片
    profile.set_preference('permissions.default.image', 2)
    profile.set_preference('browser.migration.version', 9001)
    # 禁用css
    profile.set_preference('permissions.default.stylesheet', 2)
    # 禁用flash
    profile.set_preference('dom.ipc.plugins.enabled.libflashplayer.so', 'false')
    
    # 如果EnableBrowserCookies的值設為False,那么禁用cookies
    if hasattr(self, "EnableBrowserCookies") and self.EnableBrowserCookies:
      # •值1 - 阻止所有第三方cookie。
      # •值2 - 阻止所有cookie。
      # •值3 - 阻止來自未訪問網站的cookie。
      # •值4 - 新的Cookie Jar策略(阻止對跟蹤器的存儲訪問)
      profile.set_preference("network.cookie.cookieBehavior", 2)
    
    # 默認是無頭模式,意思是瀏覽器將會在后臺運行,也是為了加速scrapy
    # 我們可不想跑著爬蟲時,旁邊還顯示著瀏覽器訪問的頁面
    # 調試的時候可以把SetHeadless設為False,看一下跑著爬蟲時候,瀏覽器在干什么
    if self.SetHeadless:
      # 無頭模式,無UI
      options.add_argument('-headless')

    # 禁用gpu加速
    options.add_argument('--disable-gpu')

    return webdriver.Firefox(firefox_profile=profile, options=options)

  def selenium_func(self, request):
    """
    在返回瀏覽器渲染的html前做一些事情
      1.比如等待瀏覽器頁面中的某個元素出現(xiàn)后,再返回渲染后的html;
      2.比如將頁面切換進iframe中的頁面;
    
    在需要使用的子類中要重寫該方法,并利用self.browser操作瀏覽器
    """
    pass

  def closed(self, reason):
    # 在爬蟲關閉后,關閉瀏覽器的所有tab頁,并關閉瀏覽器
    self.browser.quit()
    
    # 日志記錄一下
    self.logger.info("selenium已關閉瀏覽器...")

之所以不把獲取瀏覽器的具體代碼寫在__init__方法里,是因為筆者之前寫的代碼里考慮過

  • 兩種瀏覽器的調用(支持firefox與chrome),雖然后來感覺還是firefox比較方便,因為所有版本的火狐瀏覽器的驅動都是一樣的,但是谷歌瀏覽器是不同版本的瀏覽器必須用不同版本的驅動(坑爹啊- -'')
  • 自動區(qū)分不同的操作系統(tǒng)并選擇對應操作系統(tǒng)的瀏覽器驅動

額… 所以上面spider的代碼是精簡過的版本

備注: 針對selenium做了一系列的優(yōu)化加速,啟用了無頭模式,禁用了css、flash、圖片、gpu加速等… 因為爬蟲嘛,肯定是跑的越快越好啦?

3.3 自定義下載器中間件

#!usr/bin/env python 
# -*- coding:utf-8 _*-
""" 
@author:Joshua 
@description:
  負責返回瀏覽器渲染后的Response
"""
import hashlib
import time
from scrapy.http import HtmlResponse
from twisted.internet import defer, threads
from tender_scrapy.extendsion.selenium.spider import SeleniumSpider
from tender_scrapy.extendsion.selenium.requests import SeleniumRequest


class SeleniumDownloaderMiddleware(object):
  """
  Selenium下載器中間件
  """
  
  def process_request(self, request, spider):
    # 如果spider為SeleniumSpider的實例,并且request為SeleniumRequest的實例
    # 那么該Request就認定為需要啟用selenium來進行渲染html
    if isinstance(spider, SeleniumSpider) and isinstance(request, SeleniumRequest):
      # 控制瀏覽器打開目標鏈接
      browser.get(request.url)
      
      # 在構造渲染后的HtmlResponse之前,做一些事情
      #1.比如等待瀏覽器頁面中的某個元素出現(xiàn)后,再返回渲染后的html;
      #2.比如將頁面切換進iframe中的頁面;
      spider.selenium_func(request)
      
      # 獲取瀏覽器渲染后的html
      html = browser.page_source
      
      # 構造Response
      # 這個Response將會被你的爬蟲進一步處理
      return HtmlResponse(url=browser.current_url, request=request, body=html.encode(), encoding="utf-8")

這里要說一下下載器中間件的process_request方法,當每個request通過下載中間件時,該方法被調用。

  1. process_request() 必須返回其中之一: 返回 None 、返回一個 Response 對象、返回一個 Request 對象或raise IgnoreRequest 。
  2. 如果其返回 Response 對象,Scrapy將不會調用 任何 其他的 process_request() 或 process_exception() 方法,或相應地下載函數(shù); 其將返回該response。 已安裝的中間件的 process_response() 方法則會在每個response返回時被調用。

更詳細的關于下載器中間件的資料 -> https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/downloader-middleware.html#id2

3.4 額外的工具

眼尖的讀者可能注意到SeleniumSpider類里有個selenium_func方法,并且在SeleniumDownloaderMiddleware的process_request方法返回Resposne之前調用了spider的selenium_func方法

這樣做的好處是,我們可以在構造渲染后的HtmlResponse之前,做一些事情(比如…那種…很騷的那種…你懂的)

  • 比如等待瀏覽器頁面中的某個元素出現(xiàn)后,再返回渲染后的html;
  • 比如將頁面切換進iframe中的頁面,然后返回iframe里面的html(夠騷嗎);

等待某個元素出現(xiàn),然后再返回渲染后的html這種操作很常見的,比如你訪問一篇文章,它的正文是ajax加載然后js添加到html里的,ajax是需要時間的,但是selenium并不會等待所有請求都完畢后再返回

解決方法:

  1. 您可以通過browser.implicitly_wait(30),來強制selenium等待30秒(無論元素是否加載出來,都必須等待30秒)
  2. 可以通過等待,直到某個元素出現(xiàn),然后再返回html

所以筆者對<等待某個元素出現(xiàn)>這一功能做了進一步的封裝,代碼如下

#!usr/bin/env python 
# -*- coding:utf-8 _*-
""" 
@author:Joshua 
@description:
"""
import functools
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC


def waitFor(browser, select_arg, select_method, timeout=2):
  """
  阻塞等待某個元素的出現(xiàn)直到timeout結束

  :param browser:瀏覽器實例
  :param select_method:所使用的選擇器方法
  :param select_arg:選擇器參數(shù)
  :param timeout:超時時間
  :return:
  """
  element = WebDriverWait(browser, timeout).until(
    EC.presence_of_element_located((select_method, select_arg))
  )


# 用xpath選擇器等待元素
waitForXpath = functools.partial(waitFor, select_method=By.XPATH)

# 用css選擇器等待元素
waitForCss = functools.partial(waitFor, select_method=By.CSS_SELECTOR)

waitForXpath與waitForCss 是waitFor函數(shù)的兩個偏函數(shù),意思這兩個偏函數(shù)是設置了select_method參數(shù)默認值的waitFor函數(shù),分別應用不同的選擇器來定位元素

4. 中間件當然要在settings中激活一下

在這里插入圖片描述

在我們scrapy項目的settings文件中的DOWNLOADER_MIDDLEWARES字典中添加到適當?shù)奈恢眉纯?/p>

5. 使用示例

5.1一個完整的爬蟲示例

# -*- coding: utf-8 -*-
"""
@author:Joshua
@description:
  整合selenium的爬蟲示例
"""
import scrapy
from my_project.requests import SeleniumRequest
from my_project.spider import SeleniumSpider
from my_project.tools import waitForXpath


# 這個爬蟲類繼承了SeleniumSpider
# 在爬蟲跑起來的時候,將啟動一個瀏覽器
class SeleniumExampleSpider(SeleniumSpider):
  """
  這一網站,他的列表頁是靜態(tài)的,但是內容頁是動態(tài)的
  所以,用selenium試一下,目標是扣出內容頁的#content
  """
  name = 'selenium_example'
  allowed_domains = ['pingdingshan.hngp.gov.cn']
  url_format = 'http://pingdingshan.hngp.gov.cn/pingdingshan/ggcx?appCode=H65&channelCode=0301&bz=0&pageSize=20&pageNo={page_num}'

  def start_requests(self):
    """
    開始發(fā)起請求,記錄頁碼
    """
    start_url = self.url_format.format(page_num=1)
    meta = dict(page_num=1)
    # 列表頁是靜態(tài)的,所以不需要啟用selenium,用普通的scrapy.Request就可以了
    yield scrapy.Request(start_url, meta=meta, callback=self.parse)

  def parse(self, response):
    """
    從列表頁解析出正文的url
    """
    meta = response.meta
    all_li = response.css("div.List2>ul>li")

    # 列表
    for li in all_li:
      content_href = li.xpath('./a/@href').extract()
      content_url = response.urljoin(content_href)
      # 內容頁是動態(tài)的,#content是ajax動態(tài)加載的,所以啟用一波selenium
      yield SeleniumRequest(url=content_url, meta=meta, callback=self.parse_content)

    # 翻頁
    meta['page_num'] += 1
    next_url = self.url_format.format(page_num=meta['page_num'])
    # 列表頁是靜態(tài)的,所以不需要啟用selenium,用普通的scrapy.Request就可以了
    yield scrapy.Request(url=next_url, meta=meta, callback=self.parse)

  def parse_content(self, response):
    """
    解析正文內容
    """
    content = response.css('#content').extract_first()
    yield dict(content=content)
   
  def selenium_func(self, request):
    # 這個方法會在我們的下載器中間件返回Response之前被調用
    
    # 等待content內容加載成功后,再繼續(xù)
    # 這樣的話,我們就能在parse_content方法里應用選擇器扣出#content了
    waitForXpath(self.browser, "http://*[@id='content']/*[1]")

5.2 更騷一點的操作…

假如內容頁的目標信息處于iframe中,我們可以將窗口切換進目標iframe里面,然后返回iframe的html

要實現(xiàn)這樣的操作,只需要重寫一下SeleniumSpider子類中的selenium_func方法

要注意到SeleniumSpider中的selenium_func其實是啥也沒做的,一個pass,所有的功能都在子類中重寫

def selenium_func(self, request):
  # 找到id為myPanel的iframe
  target = self.browser.find_element_by_xpath("http://iframe[@id='myPanel']")
  # 將瀏覽器的窗口切換進該iframe中
  # 那么切換后的self.browser的page_source將會是iframe的html
  self.browser.switch_to.frame(target)

6. selenium的一些替代(一些解決動態(tài)頁面別的方法)

scrapy官方推薦的scrapy_splash

優(yōu)點

  • 是異步的
  • 可以將部署scrapy的服務器與部署splash的服務器分離開
  • 留給讀者遐想的空間

本人覺得的缺點

  • 喂喂,lua腳本很麻煩好嗎…(大牛請別打我)

最新的異步pyppeteer操控瀏覽器

優(yōu)點

  • 調用瀏覽器是異步的,操控的單位是tab頁,速度更快
  • 留給讀者遐想的空間

本人覺得的缺點

  • 因為pyppeteer是python版puppeteer,所以puppeteer的一些毛病,pyppeteer無可避免的完美繼承
  • 筆者試過將pyppeteer整合至scrapy中,在異步中,scrapy跑起來爬蟲,總會偶爾timeout之類的…

anyway,上面兩個都是不錯的替代,有興趣的讀者可以試一波

7. scrapy整合selenium的一些缺點

  • selenium是阻塞的,所以速度會慢些
  • 對于一些稍微簡單的動態(tài)頁面,最好還是自己去解析一下接口,不要太過依賴selenium,因為selenium帶來便利的同時,是更多資源的占用
  • 整合selenium的scrapy項目不宜大規(guī)模的爬取,比如你在自己的機子上寫好了一個一個的爬蟲,跑起來也沒毛病,速度也能接受,然后你很開心地在服務器上部署了你項目上的100+個爬蟲(里面有50%左右的爬蟲啟用了selenium),當他們跑起來的時候,服務器就原地爆炸了… 為啥? 因為相當于服務器同時開了50多個瀏覽器在跑,內存頂不住?。ㄍ梁篮雎浴?/li>

到此這篇關于scrapy結合selenium解析動態(tài)頁面的實現(xiàn)的文章就介紹到這了,更多相關scrapy selenium解析動態(tài)頁面內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Python異步編程之新舊協(xié)程的實現(xiàn)對比

    Python異步編程之新舊協(xié)程的實現(xiàn)對比

    Python中新舊協(xié)程的實現(xiàn)方式在協(xié)程發(fā)展史上有一段交集,并且舊協(xié)程基于生成器的協(xié)程語法讓生成器和協(xié)程兩個概念混淆,所以對學習者會造成一定的困擾,本文主要說明兩種協(xié)程的實現(xiàn)方式的差異,需要的可以了解下
    2024-01-01
  • 用python + openpyxl處理excel2007文檔思路以及心得

    用python + openpyxl處理excel2007文檔思路以及心得

    最近要幫做RA的老姐寫個合并excel工作表的腳本……源數(shù)據(jù)是4000+個excel 工作表,分布在9個xlsm文件里,文件內容是中英文混雜的一些數(shù)據(jù),需要從每張表中提取需要的部分,分門別類合并到多個大的表里。
    2014-07-07
  • python采用requests庫模擬登錄和抓取數(shù)據(jù)的簡單示例

    python采用requests庫模擬登錄和抓取數(shù)據(jù)的簡單示例

    這篇文章主要介紹了python采用requests庫模擬登錄和抓取數(shù)據(jù)的簡單示例,代碼簡單卻功能強大!需要的朋友可以參考下
    2014-07-07
  • Python中使用SAX解析xml實例

    Python中使用SAX解析xml實例

    這篇文章主要介紹了Python中使用SAX解析xml實例,利用SAX解析XML文檔牽涉到兩個部分:解析器和事件處理器,需要的朋友可以參考下
    2014-11-11
  • python實現(xiàn)兩個文件夾的同步

    python實現(xiàn)兩個文件夾的同步

    這篇文章主要為大家詳細介紹了利用python實現(xiàn)兩個文件夾的同步,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • 對python For 循環(huán)的三種遍歷方式解析

    對python For 循環(huán)的三種遍歷方式解析

    今天小編就為大家分享一篇對python For 循環(huán)的三種遍歷方式解析,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-02-02
  • plt.figure()參數(shù)使用詳解及運行演示

    plt.figure()參數(shù)使用詳解及運行演示

    這篇文章主要介紹了plt.figure()參數(shù)使用詳解及運行演示,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • Python利用pyecharts實現(xiàn)數(shù)據(jù)可視化的示例代碼

    Python利用pyecharts實現(xiàn)數(shù)據(jù)可視化的示例代碼

    Pyecharts是一個用于生成 Echarts 圖表的 Python 庫,Echarts 是一個由百度開源的數(shù)據(jù)可視化工具,它提供的圖表種類豐富,交互性強,兼容性好,非常適合用于數(shù)據(jù)分析結果的展示,本文將給大家介紹Python利用pyecharts實現(xiàn)數(shù)據(jù)可視化,需要的朋友可以參考下
    2024-09-09
  • Python 中 sorted 如何自定義比較邏輯

    Python 中 sorted 如何自定義比較邏輯

    這篇文章主要介紹了Python中sorted如何自定義比較邏輯,幫助大家更好的理解和學習使用python,感興趣的朋友可以了解下
    2021-02-02
  • pytorch 修改預訓練model實例

    pytorch 修改預訓練model實例

    今天小編就為大家分享一篇pytorch 修改預訓練model實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-01-01

最新評論

上杭县| 温州市| 德清县| 喀喇| 海安县| 泰宁县| 淮南市| 礼泉县| 礼泉县| 深圳市| 集贤县| 介休市| 安乡县| 且末县| 谷城县| 锦州市| 衡南县| 惠州市| 沙坪坝区| 桦川县| 梓潼县| 呈贡县| 汉中市| 大洼县| 玉屏| 木兰县| 无为县| 韶关市| 襄樊市| 法库县| 东乌| 玛曲县| 会宁县| 若尔盖县| 达日县| 都江堰市| 称多县| 长宁县| 东乡县| 吕梁市| 隆安县|