Python - 爬蟲之Scrapy

歡迎關注微信公衆號:FSA全棧行動 👋javascript

1、scrapy 概念和流程

一、概念

Scrapy 是一個 python 編寫的,被設計用於爬取網絡數據、提取結構性數據的開源網絡爬蟲框架。html

補充:Scrapy 使用了 Twisted 異步網絡框架,能夠加快下載速度java

二、工做流程

scrapy流程圖

其流程描述以下:node

  1. 爬蟲中起始的 url 構形成 request 對象 --> 爬蟲中間件 --> 引擎 --> 調度器
  2. 調度器把 request --> 引擎 --> 下載中間件 --> 下載器
  3. 下載器發送請求,獲取 response 響應 --> 下載中間件 --> 引擎 --> 爬蟲中間件 --> 爬蟲
  4. 爬蟲提取 url 地址,組裝成 request 對象 --> 爬蟲中間件 --> 引擎 --> 調度器,重複步驟 2
  5. 爬蟲提取數據 --> 引擎 --> 管道處理和保存數據

注意:python

  • 圖中綠色線條表示數據的傳遞
  • 圖中中間件的位置決定了其做用
  • 引擎處於 4 個模塊中間,各個模塊之間相互獨立,只和引擎進行交互

三、各模塊的具體做用

模塊 做用 是否實現
Scrapy Engine(引擎) 總指揮:負責數據和信號在不一樣模塊之間傳遞 scrapy 已經實現
Scheduler(調度器) 一個隊列,存放引擎發過來的 request 請求 scrapy 已經實現
Downloader(下載器) 下載把引擎發過來的 request 請求,並返回給引擎 scrapy 已經實現
Spider(爬蟲) 處理引擎發來的 response,提取數據、url,並交給引擎 須要手寫
Item Pipeline(管道) 處理引擎傳過來的數據,好比存儲 須要手寫
Downloader Middlewares(下載中間件) 能夠自定義的下載擴展,好比設置代理 通常不用手寫
Spider Middlewares(爬蟲中間件) 能夠自定義 requests 請求和進行 response 過濾 通常不用手寫

注意:爬蟲中間件 和 下載中間件 只是運行邏輯的位置不一樣,做用是重複的:如替換 UA 等。git

2、scrapy 入門使用

一、安裝 scrapy

scrapy 有 2 種安裝方式:es6

  • 命令:github

    sudo apt-get install scrapy
    複製代碼
  • pip:web

    pip/pip3 install scrapy
    複製代碼

二、項目開發流程

  1. 建立項目:ajax

    scrapy startproject <項目名稱>
    
    eg:
    	scrapy startproject myspider
    複製代碼
  2. 生成一個爬蟲:

    scrapy genspider <爬蟲名字> <容許爬取的域名>
    
    eg:
    	cd myspider
    	scrapy genspider example example.com
    複製代碼
    • 爬蟲名字:做用爬蟲運行時的參數

    • 容許爬取的域名:爲對於爬蟲設置的爬取範圍,設置以後用於過濾要爬取的 url,若是爬取的 url 與容許的域不一樣則被過濾掉

      注意:執行完命令後,myspider/spiders 目錄下,會多出一個 example.py 爬蟲文件

  3. 提取數據:

    根據網站結構在 spider 中實現數據採集相關內容

  4. 保存數據:

    使用 pipeline 進行數據後續處理和保存

  5. 運行 scrapy

    scrapy crawl <爬蟲名字>
    scrapy crawl <爬蟲名字> --nolog
    
    eg:
    	scrapy crawl example
    複製代碼

    注意 :須要在項目錄下執行命令;--nolog 能夠關閉框架日誌信息輸出

三、三個內置對象

  • request 請求對象:由 url method post_data headers 等構成
  • response 響應對象:由 url body status headers 等構成
  • item 數據對象:本質是字典

1)定位元素以及提取數據

解析並獲取 scrapy 爬蟲中的數據:利用 xpath 規則字符串進行定位和提取

  • response.xpath():返回一個相似 List 的類型,其中包含的是 selector 對象,操做和列表同樣,可是有一些額外方法:
    • extract():返回一個包含有字符串的列表
    • extract_first():返回列表中的第一個字符串,列表爲空則返回 None

2)response 響應對象的經常使用屬性

  • response.url:當前響應的 url 地址
  • response.request.url:當前響應對應的請求的 url 地址
  • response.headers:響應頭
  • response.request.headers:當前響應的請求頭
  • response.body:響應體,也就是 html 代碼,byte 類型
  • response.status:響應狀態碼

四、入門實戰

1)建立項目&爬蟲

scrapy startproject myspider
cd myspider
scrapy genspider itcast itcast.cn
複製代碼

2)完善爬蟲(抓取數據)

在/myspider/myspider/spiders/itcast.py 中修改內容以下:

import scrapy

class ItcastSpider(scrapy.Spider):
    name = 'itcast'
    # 2.檢查域名
    allowed_domains = ['itcast.cn']
    # 1.修改起始url
    start_urls = ['http://itcast.cn/channel/teacher.shtml']  # 設置起始的url,咱們只須要設置就好,一般會被自動的建立成請求發送

    # 3.在parse方法中實現爬取邏輯
    def parse(self, response):
        # 解析方法,一般用於起始url對應響應的解析
        # 定義對於網站的相關操做
        # with open('itcast.html', 'wb') as f:
        # f.write(response.body)

        # 獲取全部老師節點
        node_list = response.xpath('//div[@class="li_txt"]')

        # 遍歷老師節點列表
        for node in node_list:
            temp = {}

            # xpath方法返回的是選擇器對象列表,extract()用於從選擇器對象中提取數據
            # temp['name'] = node.xpath('./h3/text()')[0].extract()
            # temp['title'] = node.xpath('./h4/text()')[0].extract()
            # temp['desc'] = node.xpath('./p/text()')[0].extract()

            # xpath結果爲只含有一個值的列表,可使用extract_first(),若是爲多個值則使用extract()
            temp['name'] = node.xpath('./h3/text()').extract_first()
            temp['title'] = node.xpath('./h4/text()').extract_first()
            temp['desc'] = node.xpath('./p/text()').extract_first()

            # {'name': [<Selector xpath='./h3/text()' data='黃老師'>], 'title': [<Selector xpath='./h4/text()' data='高級講師'>], 'desc': [<Selector xpath='./p/text()' data='15年+的軟件開發與教學經驗,參與中國聯通VOP系統,中國聯通在線計費系統...'>]}
            # extract()以後:
            # {'name': '黃老師', 'title': '高級講師', 'desc': '15年+的軟件開發與教學經驗,參與中國聯通VOP系統,中國聯通在線計費系統,道路交通事故救助基金管理系統等的開發工做;\r\n擁有豐富的教學經驗。'}
            print(temp)

            yield temp
複製代碼

注意:

  • scrapy.Spider爬蟲類中必須有名爲 parse 的解析
  • 若是網站結構層次比較複雜,也能夠自定義其餘解析函數
  • 在解析函數中提取的 url 地址若是要發送請求,則必須屬於 allowed_domains 範圍內,可是 start_urls 中的 url 地址不受這個限制
  • parse() 函數中使用 yield 返回數值。
  • yield 可以傳遞的對象只能是:BaseItem,Request,dict,None

3)完善管道(保存數據)

  1. 定義一個管道類
  2. 重寫管道類的 process_item() 方法
  3. process_item() 方法處理完 item 以後必須返回給引擎

這裏直接使用建立項目時默認建立好的管道類:/myspider/myspider/pipelines/MyspiderPipeline,修改內容以下:

import json


class MyspiderPipeline:

    def __init__(self):
        self.file = open('itcast.json', 'w')

    def process_item(self, item, spider):
        # print('item:', item)
        # 將字典數據序列化
        json_data = json.dumps(item, ensure_ascii=False) + ',\n'

        # 將數據寫入文件
        self.file.write(json_data)

        # 默認使用完管道以後須要將數據返回給引擎
        return item

    def __del__(self):
        self.file.close()
複製代碼

4)啓用管道

ITEM_PIPELINES = {
   'myspider.pipelines.MyspiderPipeline': 300,
   # 'myspider.pipelines.MyspiderPipelineOther': 301,
}
複製代碼
  • 配置項中,鍵爲使用的管道類,管道類引用聲明使用.進行分割,分別爲:項目目錄.文件.管理類
  • 配置項中,值爲管道的使用順序,設置的數值越小越先執行,該值通常設置在 1000 之內。

5)運行爬蟲

在控制檯運行以下命令便可運行爬蟲:

scrapy crawl itcast
scrapy crawl itcast --nolog
複製代碼

3、scrapy 數據建模(items)

一、數據建模

一般在項目開發過程當中,須要在 items.py 中進行數據建模

1)爲何要建模

  • 定義 item 即提早規劃好哪些字段須要抓取,防止手誤,由於定義好以後,在運行過程當中,系統會自動檢查
  • 配置註釋一塊兒能夠清晰的知道要抓取哪些字段,沒有定義的字段不能抓取,在目標字段少的時候可使用字典代替
  • 使用 scrapy 的一些特定組件中須要 Item 作支持,如 scrapy 的 ImagesPipeline 管道類。

2)如何建模

items.py 文件中定義發提取的字段:

import scrapy

class MyspiderItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()  # 講師的名字
    title = scrapy.Field()  # 講師的職稱
    desc = scrapy.Field()  # 講師的介紹

# if __name__ == '__main__':
# item = MyspiderItem()
# item['name'] = 'lqr' # ok
# item['nama'] = 'lqr' # KeyError: 'MyspiderItem does not support field: nama'
複製代碼

注意:scrapy.Item 能夠理解爲一個更高級的 「字典」,能夠對鍵名進行限制、校驗。但切記它不是字典,若是你須要對字典進行操做,可使用 dict()scrapy.Item 進行強制轉換。

3)使用模板類

模板類定義之後,須要在爬蟲中導入而且實例化,以後的使用方法和字典相同:

# 若是報包報錯有2種解決辦法:
# 一、PyCharm以myspider爲根目錄從新打開項目
# 二、不以myspider爲根目錄的話,能夠右鍵myspider-->Make Directory as-->Sources Root
from myspider.items import MyspiderItem

    def parse(self, response):
        item = MyspiderItem()
        ...
        item['name'] = node.xpath('./h3/text()').extract_first()
        item['title'] = node.xpath('./h4/text()').extract_first()
        item['desc'] = node.xpath('./p/text()').extract_first()
		...
        yield item
複製代碼

4)開發流程總結

  1. 建立項目

    scrapy startproject 項目名
    複製代碼
  2. 明確目標

    • 在 items.py 文件中進行建模
  3. 建立爬蟲

    • 建立爬蟲

      scrapy genspider 爬蟲名 容許的域
      複製代碼
    • 編寫爬蟲

      修改start_urls
      檢查修改allowed_domains
      編寫解析方法
      複製代碼
  4. 保存數據

    • pipelines.py 文件中定義對數據處理的管道
    • settings.py 文件中註冊啓用管道

4、scrapy 處理翻頁(Request)

一、思路

  1. 找到下一頁的 url 地址
  2. 構造 url 地址的請求對象,傳遞給引擎

二、實現步驟

  1. 肯定 url 地址

  2. 構造請求

    scrapy.Request(url, callback)
    複製代碼
    • callback:指定解析函數名稱,表示該請求返回的響應使用哪個函數進行解析
  3. 把請求交給引擎

三、實戰(網易招聘爬蟲)

1)思路分析

  1. 獲取首頁數據
  2. 尋找下一頁地址,進行翻頁,獲取數據

2)代碼實現

  • 建立項目
scrapy startproject wangyi

cd wangyi
scrapy gespider job 163.com
複製代碼
  • 定義模板類
import scrapy


class WangyiItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    link = scrapy.Field()
    depart = scrapy.Field()
    category = scrapy.Field()
    type = scrapy.Field()
    address = scrapy.Field()
    num = scrapy.Field()
    date = scrapy.Field()
複製代碼
  • 編寫爬蟲(抓取數據)
import scrapy
from wangyi.items import WangyiItem


class JobSpider(scrapy.Spider):
    name = 'job'
    # 2.檢查修改allowed_domains
    allowed_domains = ['163.com']
    # 1.修改start_urls
    start_urls = ['https://hr.163.com/position/list.do']

    def parse(self, response):
        # 提取數據
        # 獲取全部職位節點列表
        node_list = response.xpath('//*[@class="position-tb"]/tbody/tr')

        # 遍歷節點列表
        for num, node in enumerate(node_list):
            # 設置過濾條件,將目標節點獲取出來
            if num % 2 == 0:
                item = WangyiItem()
                item['name'] = node.xpath('./td[1]/a/text()').extract_first()
                # item['link'] = 'https://hr.163.com' + node.xpath('./td[1]/a/@href').extract_first()
                # response.urljoin()用於拼接相對路徑的url,能夠理解爲自動補全
                item['link'] = response.urljoin(node.xpath('./td[1]/a/@href').extract_first())
                item['depart'] = node.xpath('./td[2]/text()').extract_first()
                item['category'] = node.xpath('./td[3]/text()').extract_first()
                item['type'] = node.xpath('./td[4]/text()').extract_first()
                item['address'] = node.xpath('./td[5]/text()').extract_first()
                item['num'] = node.xpath('./td[6]/text()').extract_first().strip()
                item['date'] = node.xpath('./td[7]/text()').extract_first()
                # print(item)
                yield item

        # 模擬翻頁
        part_url = response.xpath('/html/body/div[2]/div[2]/div[2]/div/a[last()]/@href').extract_first()
        # 判斷終止條件
        if part_url != 'javascript:void(0)':
            next_url = response.urljoin(part_url)
            # 構建請求對象,而且返回給引擎
            yield scrapy.Request(
                url=next_url,
                callback=self.parse
            )
複製代碼

注意:引擎根據爬蟲 yield 的對象類型,將對象分配給對應的模塊處理,好比:若是是模板類對象或字典,則會交給管道(Item Pipeline);若是是 Request 對象,則會交給隊列(Scheduler)。

  • 編寫管道(存儲數據)
import json


class WangyiPipeline:

    def __init__(self):
        self.file = open('wangyi.json', 'w')

    def process_item(self, item, spider):
        item = dict(item)

        str_data = json.dumps(item, ensure_ascii=False) + ',\n'

        self.file.write(str_data)

        return item

    def __del__(self):
        self.file.close()
複製代碼
  • 啓用管道
ITEM_PIPELINES = {
    'wangyi.pipelines.WangyiPipeline': 300,
}
複製代碼
  • 運行爬蟲
scrapy crawl job --nolog
複製代碼

3)擴展:

  • 能夠在 settings 中設置 ROBOTS 協議

    # False表示忽略網站的robots.txt協議,默認爲True
    ROBOTSTXT_OBEY = False
    複製代碼
  • 能夠在 settings 中設置 User-Agent:

    # scrapy發送的每個請求的默認UA都是設置的這個User-Agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
    複製代碼

四、scrapy.Request 的更多參數

scrapy.Request(url[, callback, method="GET", headers, body, cookies, meta, dont_filter=False])
複製代碼

注意:中括號[]裏的參數爲可選參數

參數解釋:

  • callback:表示當前 url 的響應交給哪一個函數去處理
  • meta:實現數據在不一樣的解析函數中傳遞,meta 默認帶有部分數據,好比下載延遲,請求深度等
  • dont_filter:默認爲 False,會過濾請求的 url 地址,即請求過的 url 地址不會繼續被請求,對須要重複請求的 url 地址能夠把它設置爲 True,好比貼吧的翻頁請求,頁面的數據老是在變化,start_urls 中的地址會被反覆請求,不然程序不會啓動
  • method:指定 POST 或 GET 請求
  • headers:接收一個字典,其中不包括 cookies
  • cookies:接收一個字典,專門放置 cookies
  • body:接收 json 字符串,爲 POST 的數據,發送 payload_post 請求時使用

1)meta 參數的使用

  • meta 的使用:

    • meta 能夠實現數據在不一樣的解析函數中傳遞
  • meta 的注意事項:

    • meta 參數是一個字典
    • meta 字典中有一個固定的鍵 proxy,表示代理 ip
  • 使用舉例:

def parse(self, response):
    ...
    yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item})
    ...


def parse_detail(self,response):
    # 獲取以前傳入的item
    item = response.meta['item']
複製代碼

5、scrapy 模擬登陸(cookie)

一、模擬登陸的不一樣實現方式

  • requests 模塊
    • 直接攜帶 cookies 請求頁面
    • 找到 url 地址,發送 post 請求存儲 cookie
  • selenium(瀏覽器自動處理 cookie)
    • 找到對應的 input 標籤,輸入文本點擊登陸
  • scrapy
    • 直接攜帶 cookies
    • 找 url 地址,發送 post 請求存儲 cookie

二、scrapy 直接攜帶 cookies 獲取須要登陸信息的頁面

應用場景:

  • cookie 過時時間很長,常見於一些不規範的網站
  • 能在 cookie 過時以前把全部的數據拿到
  • 配合其餘程序使用,好比其使用 selenium 把登陸以後的 cookie 獲取到並保存到本地,scrapy 發送請求以前先讀取本地 cookie

一、start_url 攜帶 cookie(重寫 start_requests 方法)

scrapy 中 start_url 是經過 start_requests 來進行處理的,可能經過重寫該方法讓 start_url 攜帶上請求頭信息,實現代碼以下:

import scrapy


class Git1Spider(scrapy.Spider):
    name = 'git1'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/GitLqr']

    def start_requests(self):
        """ 重寫start_requests,發送攜帶cookies的Request。 默認start_requests只是普通的get請求,不會攜帶自定義的頭信息 """
        url = self.start_urls[0]

        temp = '_octo=GH1.1.1045146750.1615451260; _device_id=cd8d64981fcb3fd4ba7f587873e97804'
        # 把cookies字符串轉成字典
        cookies = {data.split('=')[0]: data.split('=')[-1] for data in temp.split('; ')}

        yield scrapy.Request(
            url=url,
            callback=self.parse,
            cookies=cookies
        )

    def parse(self, response):
        print(response.xpath('/html/head/title/text()').extract_first())
複製代碼

注意:

  • scrapy 中 cookie 不可以放在 headers 中,在構造請求的時候有專門的 cookies 參數,可以接收字典形式的 cookie
  • 可能須要在 settings 中設置 ROBOTS 協議、USER_AGENT

二、scrapy.Request 發送 post 請求

scrapy.Request 發送 post 請求有兩種方式:

  • 經過 scrapy.Request() 指定 method、body 參數來發送 post 請求(不推薦)

  • 使用 scrapy.FormRequest() 來發送 post 請求(推薦)

注意:scrapy.FormRequest() 可以發送表單和 ajax 請求,參考閱讀 www.jb51.net/article/146…

舉例:

import scrapy


class Git2Spider(scrapy.Spider):
    name = 'git2'
    allowed_domains = ['github.com']
    start_urls = ['http://github.com/login']

    def parse(self, response):
        username = 'GitLqr'
        password = 'balabala'

        # 從登陸頁面響應中解析出post數據
        token = response.xpath('//input[@name="authenticity_token"]/@value').extract_first()

        post_data = {
            'commit': 'Sign in',
            'authenticity_token': token,
            'login': username,
            'password': password,
            'webauthn-support': 'supported',
        }
        print(post_data)

        # 針對登陸url發送post請求
        yield scrapy.FormRequest(
            url='https://github.com/session',
            callback=self.after_login,
            formdata=post_data
        )

    def after_login(self, response):
        yield scrapy.Request('https://github.com/GitLqr', callback=self.check_login)

    def check_login(self, response):
        print(response.xpath('/html/head/title/text()').extract_first())
複製代碼

注意:在 settings.py 中經過設置 COOKIES_DEBUG = True 可以在終端查看到 cookie 的傳遞過程

6、scrapy 管道的使用

一、pipeline 中經常使用的方法

  • process_item(self, item, spider)
    • 管道類中必須有的函數
    • 實現對 item 數據的處理
    • 必須 return item
  • open_spider(self, spider):在爬蟲開啓的時候僅執行一次
  • close_spider(self, spider):在爬蟲關閉的時候僅執行一次

注意:以上 3 個方法,能夠經過 spider.name 獲取爬蟲的名字

二、pipeline 判斷數據來源

scrapy 的 Item Pipeline 模塊能夠有多個管道,當有一個 spider 把數據對象經過引擎交給 Item Pipeline 模塊時, Item Pipeline 模塊中的全部管道會按 settings.py 中指定的管道順序一一被執行。但不少時候,咱們須要管道針對特定爬蟲作數據存儲的,這時就須要在管道中對數據對象的來源作判斷。

注意:不一樣的 pipeline 可以對一個或多個爬蟲進行不一樣的數據處理的操做,好比一個進行數據清洗,一個進行數據的保存

舉例:

  • 爬蟲 1:job.py
import scrapy
from wangyi.items import WangyiItem


class JobSpider(scrapy.Spider):
    name = 'job'
    ...

    def parse(self, response):
        ...
        yield item
複製代碼
  • 爬蟲 2:job_simple.py
import scrapy
from wangyi.items import WangyiSimpleItem


class JobSimpleSpider(scrapy.Spider):
    name = 'job_simple'
    ...

    def parse(self, response):
        ...
        yield item
複製代碼
  • 2 個管道:pipelines.py
class WangyiPipeline:

    def open_spider(self, spider):
        if spider.name == 'job':
            self.file = open('wangyi.json', 'w')

    def process_item(self, item, spider):
        if spider.name == 'job':
            item = dict(item)
            str_data = json.dumps(item, ensure_ascii=False) + ',\n'
            self.file.write(str_data)
        return item

    def close_spider(self, spider):
        if spider.name == 'job':
            self.file.close()


class WangyiSimplePipeline:

    def open_spider(self, spider):
        if spider.name == 'job_simple':
            self.file = open('wangyi_simple.json', 'w')

    def process_item(self, item, spider):
        if spider.name == 'job_simple':
            item = dict(item)
            str_data = json.dumps(item, ensure_ascii=False) + ',\n'
            self.file.write(str_data)
        return item

    def close_spider(self, spider):
        if spider.name == 'job_simple':
            self.file.close()
複製代碼

三、pipeline 使用注意點

  • 使用以前須要在 settings 中開啓
  • pipeline 在 settings 中鍵表示位置(即 pipeline 在項目中的位置能夠自定義),值表示距離引擎的遠近,越近數據會越先通過:權重值小的優先執行
  • 有多個 pipeline 的時候,process_item 的方法必須 return item,不然後一個 pipeline 取到的數據爲 None 值
  • pipeline 中 process_item 方法必需要有,不然 item 沒有辦法接收和處理
  • process_item 方法接收 item 和 spider,其中 spider 表示當前傳遞 item 過來的 spider
  • open_spider(spider):可以在爬蟲開啓的時候執行一次
  • close_spider(spider):可以在爬蟲關閉的時候執行一次
  • 上述倆個方法常常用於爬蟲和數據庫的交互,在爬蟲開啓的時候創建和數據庫的鏈接,在爬蟲關閉的時候斷開和數據庫的鏈接

7、scrapy 中間件

一、介紹

1)分類

根據 scrapy 運行流程中所在位置不一樣,對 scrapy 中間件進行分類:

  • 下載中間件
  • 爬蟲中間件

2)做用

scrapy 中間件的做用是:預處理 request 和 response 對象

  • 對 header 以及 cookie 進行更換和處理
  • 使用代理 ip 等
  • 對請求進行定製化操做

3)比較

  • 默認狀況下,兩種中間件都在 middlewares.py 一個文件中

  • 爬蟲中間件使用方法和下載中間件相同,且功能重複,一般使用下載中間件

二、使用方法

注意:結合 scrapy 流程圖可方便理解

scrapy流程圖

中間件使用步驟以下:

  1. middlerware.py 中定義中間件類

  2. 在中間件類中,重寫處理請求或者響應的方法(以Downloader Middlewares爲例)

    • process_request(self, request, spider)

      當每一個 request 經過下載中間件時,該方法被調用【Scrapy Engine --> Downloader】

      • 返回 None【繼續】:該 request 對象經過引擎傳遞給其餘權重低的 process_request 方法,若是全部中間件都返回 None,則請求最終被交給下載器處理。注意:沒有 return 也是返回 None。
      • 返回 Request 對象【中斷】:把 request 對象經過引擎交給調度器,此時將不經過其餘權重低的 process_request 方法
      • 返回 Response 對象【中斷】:請求不會達到下載器,會直接把 response 經過引擎交給 Spider 進行解析
    • process_response(self, request, response, spider)

      當下載器完成 http 請求,傳遞響應給引擎的時候調用【Scrapy Engine <-- Downloader】

      • 返回 Request 對象【中斷】:經過引擎交給調度器繼續請求,此時將不經過其餘權重低的 process_response 方法
      • 返回 Response 對象【繼續】:經過引擎交給爬蟲處理或交給權重更低的其餘下載中間件的 process_response 方法
  3. settings 文件中開啓中間件,權重值越小越優先執行(同管道註冊同樣)

三、實現隨機 User-Agent 下載中間件

1)準備 UA 列表

settings.py 文件中定義一個存放了大量 UA 的列表 USER_AGENT_LIST

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36'

USER_AGENT_LIST = [
    'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
    'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
    'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
    'MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
    'Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10',
    'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13',
    'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+',
    'Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0',
    'Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124',
    'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)',
    'UCWEB7.0.2.37/28/999',
    'NOKIA5700/ UCWEB7.0.2.37/28/999',
    'Openwave/ UCWEB7.0.2.37/28/999',
    'Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999',
    'UCWEB7.0.2.37/28/999',
    'NOKIA5700/ UCWEB7.0.2.37/28/999',
    'Openwave/ UCWEB7.0.2.37/28/999',
    'Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999'
]
複製代碼

注意:該 USER_AGENT_LIST 變量名能夠自定義,也能夠寫在其餘 py 文件中,寫在 settings.py 文件中,只是爲了規範而已。

2)定義下載中間件

middlewares.py 文件中定義隨機 UA 的下載中間件:

import random

from Douban.settings import USER_AGENT_LIST


class RandomUserAgentMiddleware(object):
    def process_request(self, request, spider):
        # print(request.headers['User-Agent'])
        ua = random.choice(USER_AGENT_LIST)
        request.headers['User-Agent'] = ua
複製代碼

3)啓用下載中間件

settings.py 文件中配置開啓自定義的下載中間件:

DOWNLOADER_MIDDLEWARES = {
    'Douban.middlewares.RandomUserAgentMiddleware': 543,
}
複製代碼

四、實現 Selenium 渲染的下載中間件

scrapy 的 Downloader 模塊只會根據請求獲取響應,但實際開發過程當中,有些頁面上的數據是經過 ajax 延遲加載出來的,Downloader 模塊沒法應對這種狀況,這時就須要用到 Selenium 來處理這類請求,等頁面渲染完成後,再把渲染好的頁面返回給爬蟲便可:

from selenium import webdriver
from scrapy.http import HtmlResponse


class SeleniumMiddleware(object):
    def __init__(self):
        self.driver = webdriver.Chrome()

    def process_request(self, request, spider):
        url = request.url

        # 過濾須要使用selenium渲染的request
        if 'daydata' in url:
            self.driver.get(url)
            time.sleep(3)
            data = self.driver.page_source
            self.driver.close()

            # 返回HtmlResponse,請求不會達到Downloader,而是直接經過引擎交給爬蟲
            response = HtmlResponse(url=url, body=data, encoding='utf-8', request=request)
            return response

    def __del__(self):
        self.driver.quit()
複製代碼

若是文章對您有所幫助, 請不吝點擊關注一下個人微信公衆號:FSA全棧行動, 這將是對我最大的激勵. 公衆號不只有Android技術, 還有iOS, Python等文章, 可能有你想要了解的技能知識點哦~

相關文章
相關標籤/搜索