主頁 > 後端開發 > 爬蟲之Scrapy框架

爬蟲之Scrapy框架

2020-10-09 20:52:01 後端開發

  • 框架:具有很強的通用性,且封裝了一些通用實作方法的專案模板
  • scrapy(異步框架):
    • 高性能的網路請求
    • 高性能的資料決議
    • 高性能的持久化存盤
    • 高性能的全站資料爬取
    • 高性能的深度爬取
    • 高性能的分布式

Scrapy環境安裝

IOS和Linux

  • pip install scrapy

windows

      a. pip3 install wheel

      b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
      
      # Twisted?17.1.0?cp35?cp35m?win_amd64.whl; Python是3.5版本的就選擇cp35下載

      c. 進入下載目錄,執行 pip3 install Twisted?17.1.0?cp35?cp35m?win_amd64.whl
    
      # 安裝失敗可能是這個檔案的版本導致的,即使Python版本都是對的,可以重新下載一個32位的試試
      # 還安裝失敗的話就下載其python版本的,總有一個能成功

      d. pip3 install pywin32

      e. pip3 install scrapy

安裝完成后,輸入``scrapy`測驗一下,出現如下圖顯示,即安裝成功,

Scrapy的基本使用

創建工程

  • scrapy startprojct proNmame

    cd proNmame進入到工程目錄下執行爬蟲檔案

proName		# 工程名字
	spiders		# 爬蟲包(檔案夾)
		__init__.py
    __init__.py
    items.py
    middlewares.py
    pipelines.py
    settings.py		# 創建好的工程的組態檔
scrapy.cfg		# scrapy的組態檔,不用修改

創建爬蟲檔案

  • 創建爬蟲檔案是py源檔案
  • scrapy genspider spiderName www.xxx.com 網址后期可以修改
    • spiders包下創建一個py檔案
# -*- coding: utf-8 -*-
import scrapy


class FirstSpider(scrapy.Spider):	# scrapy.Spider所有爬蟲類的父類
    # name表示的爬蟲檔案的名稱,當前爬蟲檔案的唯一標識
    name = 'first'
    
    # 允許的域名,通常會注釋掉
    # allowed_domains = ['www.xx.com']
    
    # 起始的url串列,最開始要爬的網址串列
    # 作用:可以將內部的串列元素進行get請求的發送
    start_urls = ['http://www.sougou.com/','www.baidu.com']

    # 呼叫parse方法決議資料,方法呼叫的次數由start_urls串列元素個數決定的
    def parse(self, response):	# response表示一個回應物件,
        pass

基本配置

  • UA偽裝

  • robots協議的不遵從

    settings.py中將ROBOTSTXT_OBEY = True修改為False

  • 指定日志等級

    settings.py中添加LOG_LEVEL = 'ERROR'

執行工程

  • scrapy crawl spiderName

  • 執行工程是不展示日志檔案

    scrapy crawl spiderName --nolog

    這種方式下程式報錯,不會展示;設定好日志等級后直接執行工程即可,

資料決議

  • response.xpath('xpath運算式')

  • etree的不同之處:

    取文本/屬性:回傳的是一個Selector物件,文本資料是存盤在該物件中

    • Selector物件[0].extract()回傳字串
    • Selector物件.extract_first()回傳字串
    • Selector物件.extract()回傳串列

常用操作

  • 如果串列只有一個元素用Selector物件.extract_first(),回傳字串
  • 如果串列有多個元素Selector物件.extract(),回傳串列,串列里裝的是字串

spiderName.py檔案

# -*- coding: utf-8 -*-
import scrapy

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://duanziwang.com/']

    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath運算式決議
        for article in article_list:
            title = article.xpath('./div[1]/h1/a/text()')[0]    # 回傳一個Selector物件
            # <Selector xpath='./div[1]/h1/a/text()' data='https://www.cnblogs.com/Golanguage/p/關于健康養生、延年益壽的生活諺語_段子網收錄最新段子'>
            title = article.xpath('./div[1]/h1/a/text()')[0].extract()    # 回傳字串
            # 關于健康養生、延年益壽的生活諺語_段子網收錄最新段子
            title = article.xpath('./div[1]/h1/a/text()').extract_first()   # 回傳字串
            # 關于健康養生、延年益壽的生活諺語_段子網收錄最新段子
            title = article.xpath('./div[1]/h1/a/text()').extract()     # 回傳串列
            # ['關于健康養生、延年益壽的生活諺語_段子網收錄最新段子']
            print(title)
            break

持久化存盤

基于終端指令的持久化存盤

  • 只可以將parse方法的回傳值存盤到指定后綴的文本檔案中

    指定后綴:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle',通常用csv

    指令scrapy crawl spiderName -o filePath

案例:將文本資料持久化存盤

# -*- coding: utf-8 -*-
import scrapy

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://duanziwang.com/']

    # 基于終端指令的持久化存盤
    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath運算式決議
        all_data = https://www.cnblogs.com/Golanguage/p/[]
        for article in article_list:
            title = article.xpath('./div[1]/h1/a/text()').extract_first()
            content = article.xpath('./div[2]/p//text()').extract()
            content = ''.join(content)
            dic = {
                "title": title,
                "content": content
            }
            all_data.append(dic)
        return all_data
# 終端指令
# scrapy crawl spiderName -o duanzi.csv

基于管道的持久化存盤

? scrapy建議使用管道持久化存盤

實作流程

  • 資料決議(spiderName .py

  • 實體化item型別物件(items.py

    items.py的item類中定義相關的屬性

    fieldNmae = scrapy.Field()

  • 將決議的資料存盤封裝到item型別的物件中(spiderName .py

    item['fileName'] = value 給item物件的fieldNmae屬性賦值

  • 將item物件提交給(spiderName .py

    yield item 將item提交給優先級最高的管道

  • 在管道中接收item,可以將item中存盤的資料進行任意形式的持久化存盤(pipelines.py

    process_item():負責接收item物件且對其進行持久化存盤

  • 在組態檔settings.py中開啟管道機制

    找到如下代碼,取消注釋

    ITEM_PIPELINES = {
        # 300表示的是優先級,數值越小,優先級越高
       'duanziPro.pipelines.DuanziproPipeline': 300,
    }
    

案例:將文本資料持久化存盤

按上述在settings.py找到管道代碼,取消注釋,

spiderName .py

# -*- coding: utf-8 -*-
import scrapy
from duanziPro.items import DuanziproItem

class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://duanziwang.com/']
    # 基于管道的持久化存盤
    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath運算式決議
        for article in article_list:
            title = article.xpath('./div[1]/h1/a/text()').extract_first()
            content = article.xpath('./div[2]/pre/code//text()').extract()
            content = ''.join(content)
            print(content)
            # 實體化item物件
            item = DuanziproItem()
            # 通過中括號的形式訪問屬性給其賦值
            item['title'] = title
            item['content'] = content
            # 向管道提交item
            yield item

items.py

import scrapy

class DuanziproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 使用固有屬性定義了兩個屬性
    # Field是一個萬能資料型別
    title = scrapy.Field()
    content = scrapy.Field()

pipelines.py

class DuanziproPipeline(object):
    # 重寫父類的該方法:該方法只會在爬蟲開始的時候執行一次
    fp = None
    
    # 打開檔案
    def open_spider(self, spider):
        print('open spider')
        self.fp = open('./duanzi.txt', 'w', encoding='utf-8')

    # 關閉檔案
    def close_spider(self, spider):
        print('close spider')
        self.fp.close()

    # 接收爬蟲檔案回傳item物件,process_item方法每呼叫一次可接收一個item物件
    # item引數:接收到的某一個item物件
    def process_item(self, item, spider):
        # 取值
        title = item['title']
        content = item['content']
        self.fp.write(title + ":" + content + "\n")
        return item

管道存盤細節處理

  • 管道檔案中的管道類表示的是什么?

    一個管道類對應的就是一種存盤形式(文本檔案,資料庫)

    如果想要實作資料備份,則需要使用多個管道類(多種存盤形式:MySQL,Redis)

  • process_item中的 retutn item

    將item傳遞給下一個即將被執行(按照組態檔中ITEM_PIPELINES得權重排序)的管道類

存盤到MySQL

pipelines.py中添加如下代碼

import pymysql

class MysqlPipeline(object):
    conn = None
    cursor = None

    def open_spider(self, spider):
        self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123', db='spider',
                                    charset='utf8')

    def process_item(self, item, spider):
        # 取值
        title = item['title']
        content = item['content']
        self.cursor = self.conn.cursor()
        # sql陳述句
        sql = 'insert into duanzi values ("%s","%s")' % (title, content)
        try:
            self.cursor.execute(sql)
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()
        return item

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

settings.py中將MysqlPipeline類注冊到ITEM_PIPELINES中

ITEM_PIPELINES = {
    # 300表示的是優先級,數值越小,優先級越高
    'duanziPro.pipelines.DuanziproPipeline': 300,
    'duanziPro.pipelines.MysqlPipeline': 301,
}

存盤到Redis

  • 因為redis有的版本不支持存盤字典,下載2.10.6版本

    pip install redis==2.10.6

pipelines.py中添加如下代碼

from redis import Redis


class RedisPipeline(object):
    conn = None

    def open_spider(self, spider):
        self.conn = Redis(host='127.0.0.1', port=6379, password='yourpassword')

    def process_item(self, item, spider):
        self.conn.lpush('duanziList', item)
        # 報錯:因為redis有的版本不支持存盤字典,pip install redis==2.10.6

settings.py中將RedisPipeline類注冊到ITEM_PIPELINES中

ITEM_PIPELINES = {
    # 300表示的是優先級,數值越小,優先級越高
    'duanziPro.pipelines.DuanziproPipeline': 300,
    'duanziPro.pipelines.RedisPipeline': 301,
}

手動發送請求

  • 可以在start_urls這個串列中添加url,但是比較繁瑣

  • get請求發送

    yield scrapy.Request(url,callback)

    • url:指定好請求的url
    • callback:callback指定的回呼函式一定會被執行(資料決議)
  • post請求發送

    yield scrapy.FormRequest(url,callback,formdata)

    • formdata存放請求引數,字典型別
  • 父類中start_requests請求發送的原理

# 簡單模擬父類的方法,主要看yield
def start_requests(self):
    for url in self.start_urls:
        # 發起get請求
        yield scrapy.Request(url=url,callback=self.parse)
        # 發起post請求,formdata存放請求引數
        yield scrapy.FormRequest(url=url,callback=self.parse,formdata=https://www.cnblogs.com/Golanguage/p/{})

代碼實作

  • 主要是在spiderName .py中使用遞回方法,且明確遞回結束的條件;

    使用父類yield實作全站爬取

# -*- coding: utf-8 -*-
import scrapy
from duanziPro.items import DuanziproItem


class DuanziSpider(scrapy.Spider):
    name = 'duanzi'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://duanziwang.com/']
    
    # 手動請求的發送,對其他頁碼的資料進行請求操作
    # 定義通用url模板
    url = "https://duanziwang.com/page/%d/"
    pageNum = 2

    def parse(self, response):
        article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath運算式決議
        all_data = https://www.cnblogs.com/Golanguage/p/[]
        for article in article_list:
            title = article.xpath('./div[1]/h1/a/text()').extract_first()
            content = article.xpath('./div[2]/pre/code//text()').extract()
            content = ''.join(content)
            # 實體化item物件
            item = DuanziproItem()
            # 通過中括號的形式訪問屬性給其賦值
            item['title'] = title
            item['content'] = content
            # 向管道提交item
            yield item
        if self.pageNum < 5:
            new_url = format(self.url%self.pageNum)
            self.pageNum += 1
            # 遞回實作全站資料爬取,callback指定決議的方法
            yield scrapy.Request(url=new_url, callback=self.parse)
  • pipelines.py中實作資料持久化存盤
class DuanziproPipeline(object):
    # 重寫父類的該方法:該方法只會在爬蟲開始的時候執行一次
    fp = None

    def open_spider(self, spider):
        print('open spider')
        self.fp = open('./duanzi.txt', 'w', encoding='utf-8')

    # 關閉fp
    def close_spider(self, spider):
        print('close spider')
        self.fp.close()

    # 接收爬蟲檔案回傳item物件,process_item方法每呼叫一次可接收一個item物件
    # item引數:接收到的某一個item物件
    def process_item(self, item, spider):
        # 取值
        title = item['title']
        content = item['content']
        self.fp.write(title + ":" + content + "\n")
        # 將item轉交給下一個即將被執行的管道類
        return item
  • settings.py中開啟管道類
ITEM_PIPELINES = {
    # 300表示的是優先級,數值越小,優先級越高
    'duanziPro.pipelines.DuanziproPipeline': 300,
}

yield在scrapy中的使用

  • 向管道中提交item物件

    yield item

  • 手動請求發送

    yield scrapy.Request(url,callback)

五大核心組件

  • 引擎(Scrapy Engine)

    處理整個系統的資料流,觸發事物(框架核心),

  • 調度器(Scheduer)

    用來接收引擎發過來的請求,壓入佇列中,并在引擎再次請求的時候回傳,

  • 下載器(Downloader)

    用于下載網頁內容,并將網頁內容回傳給蜘蛛(Scrapy下載器是建立在twisted這個高效模型上的),

  • 爬蟲(Spiders)

    爬蟲主要是干活的,用于從特定的網頁中提取自己需要的資訊,即所謂的物體(item),用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面

  • 管道(item Pipeline)

    負責處理爬蟲從網頁抽取的物體,主要的功能是持久化物體、驗證物體的有效性、清除不需要的資訊,當頁面被爬蟲決議后,將被發送到專案管道,并經過幾個特定的次序處理資料,

五大核心組件的作業流程


當執行爬蟲檔案時,5大核心組件就在作業了

首先執行爬蟲檔案spider,spider的作用是
(1)決議(2)發請求,原始的url存盤在于spider中
1:當spider執行的時候,首先對起始的url發送請求,將起始url封裝成請求物件
2:將請求物件傳遞給引擎
3:引擎將請求物件傳遞給調度器(內部含有佇列和過濾器兩個機制),調度器將請求存盤在佇列(先進先出)中
4:調度器從佇列中調度出url的相應物件再將請求傳遞給引擎
5:引擎將請求物件通過下載中間件發送給下載器
6:下載器拿到請求到互聯網上去下載
7:互聯網將下載好的資料封裝到回應物件給到下載器
8:下載器將回應物件通過下載中間件發送給引擎
9:引擎將封裝了資料的回應物件回傳給spider類parse方法中的response物件
10:spider中的parse方法被呼叫,response就有了回應值
11:在spider的parse方法中進行決議代碼的撰寫;
(1)會決議出另外一批url,(2)會決議出相關的文本資料
12: 將決議拿到的資料封裝到item中
13:item將封裝的文本資料提交給引擎
14:引擎將資料提交給管道進行持久化存盤(一次完整的請求資料)
15:如果parder方法中決議到的另外一批url想繼續提交可以繼續手動進行發請求
16:spider將這批請求物件封裝提交給引擎
17:引擎將這批請求物件發配給調度器
16:這批url通過調度器中過濾器過濾掉重復的url存盤在調度器的佇列中
17:調度器再將這批請求物件進行請求的調度發送給引擎

引擎作用:
1:處理流資料 2:觸發事物
引擎根據相互的資料流做判斷,根據拿到的流資料進行下一步組件中方法的呼叫

下載中間件: 位于引擎和下載器之間,可以攔截請求和回應物件;攔截到請求和回應物件后可以
篡改頁面內容和請求和回應頭資訊,
爬蟲中間件:位于spider和引擎之間,也可以攔截請求和回應物件,不常用,

請求傳參

  • 作用

    實作深度爬取,

  • 深度爬取

    爬取的資料不在同一張頁面中,

  • 在進行手動發送請求的時候,可以將一個meta字典傳遞給callback指定的回呼函式

    • yield scrapy.Request(url,callback,meta={})

    • 在回呼函式中接收meta

      response.meta['key'] 將meta字典中key對應的value值取出

案例:電影名字和簡介爬取

  • 案例地址:4567電影網電影分類

spiderName.py

# -*- coding: utf-8 -*-
import scrapy
from moviePro.items import MovieproItem

class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567kan.com/frim/index1.html']
    # 通用url
    url = 'https://www.4567kan.com/frim/index1-%d.html'
    pagNum = 2

    # 決議首頁的資料,爬取電影名稱和簡介
    def parse(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            mv_name = li.xpath('./div/a/@title').extract_first()
            item = MovieproItem()
            item['name'] = mv_name
            detail_url = "https://www.4567kan.com/" + li.xpath('./div/a/@href').extract_first()
            yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
            # meta 就是一個字典,可以將字典傳給callback指定的回呼函式,實作請求傳參
        # 全站資訊的爬取,測驗前4頁
        if self.pagNum < 5:
            new_url = format(self.url % self.pagNum)
            self.pagNum += 1
            yield scrapy.Request(url=new_url,callback=self.parse)
    # 自定義決議方法,決議詳情頁電影簡介
    def parse_detail(self, response):
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[3]//text()').extract_first()
        # 接收請求傳參傳遞過來的item
        item = response.meta['item']
        item['desc'] = desc
        yield item

中間件

爬蟲中間件

位于spider和引擎之間,也可以攔截請求和回應物件,不常用,

下載中間件(推薦)

位于引擎和下載器之間,可以攔截請求和回應物件;攔截到請求和回應物件后可以
篡改頁面內容和請求和回應頭資訊,

作用:攔截所有請求和回應

為什么要攔截請求?

  • UA偽裝(篡改請求頭資訊)

    process_request()方法中,

    request.headers['User-Agent'] ="請求頭資訊"

  • 設定代理

    process_exception()方法中,

    request.meta['proxy'] = 'https://ip:prot'

    return request

工程專案中middlewares.py就是中間件,

from scrapy import signals
import random

# UA池
user_agent_list = [
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
    "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
    "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
    "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
    "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
    "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
    "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
    "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
    "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
    "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
    "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
    "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
    "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
    "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
    "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
    "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
    "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
PROXY_HTTP = ["ip:port"]
PROXY_HTTPS = ["ip:port"]

# 下載中間件
class MiddleproDownloaderMiddleware(object):

    def process_request(self, request, spider):
        """
        攔截正常請求
        :param request: 攔截到的正常請求
        :param spider: 爬蟲類實體化的物件
        :return: 
        """
        # UA偽裝
        request.headers['User-Agent'] = random.choice(user_agent_list)
        return None

    def process_response(self, request, response, spider):
        """
        process_response函式:攔截所有的回應
        :param request: 回應對應的請求
        :param response: 攔截到的回應
        :param spider: 爬蟲類實體化的物件
        :return: 回傳處理后的回應
        """
        return response

    def process_exception(self, request, exception, spider):
        """
        攔截發生例外的請求;對例外請求進行修正,讓其變成正常請求
        :param request: 攔截到的發生例外的請求物件
        :param exception: 攔截到的例外資訊
        :param spider: 爬蟲類實體化的物件
        :return: 將修正后的請求物件進行重新發送
        """
        # 代理操作
        if request.url.split(":")[0] == 'http':
            request.meta['proxy'] = "http:{}".format(random.choice(PROXY_HTTP))
        else:
            request.meta['proxy'] = "https:{}".format(random.choice(PROXY_HTTPS))
        return request  # 將修正后的請求物件進行重新發送

為什么要攔截回應?

  • 篡改回應資料

案例:網易新聞資料爬取

  • 實作流程:

    1,決議出5個板塊對應的url

    2,對5個板塊的url發起請求

    3,獲取板塊的頁面原始碼資料

    ? 問題:資料為動態加載,原始碼資料中沒有新聞標題和詳情頁的url

    ? 解決:將回應資料進行篡改,改成包含動態加載的資料

  • selenium幫助我們捕獲到包含了動態加載的回應資料

    selenium在scrapy中的使用

    • 實體化一個瀏覽器物件(爬蟲檔案中)
    • 在中間件process_response中進行selenium后續的操作
    • 在爬蟲檔案的爬蟲類沖重寫一個closed(self,spider),關閉瀏覽器

在settings.py基本設定一下,打開下載中間件

spiderNmae.py

# -*- coding: utf-8 -*-
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://news.163.com/']
    # 5個板塊頁面的url
    model_urls = []
    # 實體化瀏覽器物件
    bro = webdriver.Chrome(executable_path=r'D:\Reptile\jupyter\onceagain\爬蟲\Scrap框架\chromedriver.exe')

    # 資料決議,決議5個板塊對應頁面url
    def parse(self, response):
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        index = [3, 4, 6, 7, 8]
        for i in index:
            model_url = li_list[i].xpath('./a/@href').extract_first()
            self.model_urls.append(model_url)
        # 對板塊url發請求,捕獲每個頁面的原始碼資料
        for url in self.model_urls:
            yield scrapy.Request(url=url, callback=self.parse_model)

    # 決議標題和新聞詳情頁的url
    def parse_model(self, response):
        div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
        for div in div_list:
            title = div.xpath('./a/img/@alt').extract_first()
            new_detail_url = div.xpath('./a/@href').extract_first()
            if new_detail_url:
                item = WangyiproItem()
                item['title'] = title
                # 對新聞的詳情頁發請求,決議出新聞的內容
                yield scrapy.Request(url=new_detail_url, callback=self.parse_new_detail, meta={'item': item})

    # 決議新聞內容
    def parse_new_detail(self, response):
        item = response.meta['item']
        content = response.xpath('//*[@id="endText"]//text()').extract()
        content = ''.join(content)
        # print(content)
        item['content'] = content
        yield item

    # 整個程式結束時呼叫一次:父類的方法
    def closed(self, spider):
        self.bro.quit()

middlewares.py

from time import sleep


class WangyiproDownloaderMiddleware(object):

    # 攔截回應,篡改指定回應物件的回應資料
    def process_response(self, request, response, spider):
        # 獲取5個板塊對應的url
        model_urls = spider.model_urls
        bro = spider.bro
        if request.url in model_urls:  # 成立之后定位到的response就是某一個板塊對應的response
            # 指定回應資料的篡改
            # 引數body就是回應資料
            bro.get(request.url)
            sleep(1)
            page_text = bro.page_source  # 作為新的回應資料,包含動態加載資料源
            return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
        else:
            return response

items.py

import scrapy

class WangyiproItem(scrapy.Item):
    title = scrapy.Field()
    content = scrapy.Field()

Scrapy爬大文本資料

大文本資料就是量級大的二進制資料,如圖片,壓縮包,音頻,視頻...

  • 爬蟲檔案中將二進制資源的url進行爬取和決議,將其存盤到item中向管道提交

  • 在管道檔案中指定對應的管道類

    父類:from scrapy.pipelines.images import ImagesPipeline

    組態檔中進行如下操作

    # 自動創建一個指定的檔案夾
    IMAGES_STORE = './imgLib'
    

案例:校花圖片的爬取

自定義一個關于ImagesPipeline該父類的管道類,在pipelines.py中重寫如下三個方法

from scrapy.pipelines.images import ImagesPipeline
import scrapy


class ImgproPipeline(ImagesPipeline):
    # 發起請求
    def get_media_requests(self, item, info):
        imgSrc = https://www.cnblogs.com/Golanguage/p/item['imgSrc']
        # 請求傳參,將meta字典傳遞給了file_path這個方法
        yield scrapy.Request(url=imgSrc, meta={'item': item})

    # 定制get_media_request請求到資料持久化存盤的路徑(檔案夾路徑+檔案名稱)
    def file_path(self, request, response=None, info=None):
        # 通過request.meta接收請求傳參傳遞過來的meta字典
        imgName = request.meta['item']['imgName']
        return imgName
    	# 如果組態檔中沒指定檔案夾
        # return '檔案夾/%s.jpg' % (image_guid)

    def item_completed(self, results, item, info):
        return item

CrawlSpider

全站資料爬取

  • CrawlSpider就是Spider的一個子類

  • 創建一個基于CrawlSpider爬蟲檔案

    scrapy genspider -t crawl spiderName www.xxx.com

  • LinkExtractor(allow=r'正則運算式'):鏈接提取器

    • 作用:可以根據指定的指定的規則(allow)進行鏈接提取
  • Rule(link,callback,follow=True):規則決議器

    link:鏈接提取

    callback:回呼函式,字串反射呼叫函式,決議資料

    follow=True:將鏈接提取器 繼續作用到 鏈接提取器提取到的鏈接的對應頁面中

    • 作用:

      1.將鏈接提取器提取到的鏈接進行請求發送(get)請求發送

      2.請求到的資料根據指定的規則進行資料決議

  • 深度爬取

    手動發送請求決議資料,請求傳參和LinkExtractor,Rule一起使用實作深度爬取

Rule(LinkExtractor(allow=r'正則運算式提取指定url'),callback='函式名',follow=True(提取全站頁碼url)/Flase(提取當前葉頁碼url))

使用鏈接提取器提取詳情頁的url實作深度爬取

案例:陽光問政

spiderName.py中代碼

  • 問題:實體化在持久化存盤后無法實作資料一一對應的匯總
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
# 實體化了兩個item物件
from sunPro.items import TitleItem, ContentItem

class SunSpider(CrawlSpider):
    name = 'sun'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']

    # 根據正則提取全站頁碼
    link = LinkExtractor(allow=r'id=1&page=\d+')
    # 提取標題對應的詳情頁鏈接
    link_detail = LinkExtractor(allow=r'politics/index\?id=\d+')
    rules = (
        Rule(link, callback='parse_item', follow=True),
        Rule(link_detail, callback='parse_detail', follow=False),
    )

    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
        for li in li_list:
            title = li.xpath('./span[3]/a/text()').extract_first()
            detail_url =
            item = TitleItem()
            item['title'] = title
            yield item

    def parse_detail(self, response):
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
        item = ContentItem()
        item['content'] = content
        yield item
  • 解決上述問題

    手動發送請求決議詳情頁的內容,請求傳參,傳給一個item

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem


class SunSpider(CrawlSpider):
    name = 'sun'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
    link = LinkExtractor(allow=r'id=1&page=\d+')
    rules = (
        Rule(link, callback='parse_item', follow=False),
    )

    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
        for li in li_list:
            title = li.xpath('./span[3]/a/text()').extract_first()
            detail_url = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first()
            item = SunproItem()
            item['title'] = title
            yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})

    def parse_detail(self, response):
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
        item = response.meta['item']
        item['content'] = content
        yield item

分布式

實際應用中很少,一般都是面試時問道,主要是文原理,

  • 概念:搭建一個分布式機群,共同執行一組代碼,聯合對同一個資源的資料進行分布且聯合爬取

  • 實作方式:

    簡稱:scrapy + redis

    全稱:Scrapy框架 + scrapy-redis組件

  • 原生的scrapy框架無法實作分布式

    原生scrapy的調度器和管道無法共享

  • scrapy-redis組件的作用

    可以給原生的scrapy框架,提供可以被共享的管道和調度器

  • 環境安裝

    pip install scrapy-redis

  • 實作流程

    修改爬蟲檔案中爬蟲類對應的操作

    • 導包:from scrapy_redis.spiders import RedisCrawlSpider

      CrawlSpider     匯入 RedisCrawlSpider
      Spider			匯入 RedisSpider
      
    • 爬蟲類的父類修改成RedisCrawlSpider

    • 將start_urls洗掉,添加一個redis_key='可以被共享調度器佇列的名稱'

    • 進行常規的請求和決議和向管道提交item操作即可

    settings.py進行配置

      • 配置管道
      ITEM_PIPELINES = {
          # scrapy組件中有管道,是基于redis的,所以目前只能用redis存盤
          'scrapy_redis.pipelines.RedisPipeline':400,
      }
      
      • 調度器的配置
      # 增加了一個去重容器類的配置,作用使用Redis的set集合來存盤請求的指紋資料,從而實作請求去重的持久化
      DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
      # 使用scrapy-redis組件自己的調度器
      SCHEDULER = "scraoy_redis.scheduler.Scheduler"
      # 配置調度器是否要持久化,也就是爬蟲結束,是否清空Redis中請求對列和去重的set,true表示持久化存盤,不清空
      SCHEDULER_PERSIST = True
      
      • 對Redis進行配置
      REDIS_HOST = 'redis服務的ip地址'
      REDIS_PORT = 6379
      

    對redis組態檔進行修改

    • 56行:# bind 127.0.0.1
    • 75行:protected-mode no

    啟動Redis服務和客戶端

    • 攜帶組態檔啟動redis,在redis安裝目錄下運行cmd

      redis-server.exe redis.windows.conf

    • 啟動客戶端

      redis-cli

    啟動程式

    • 在終端中進入到爬蟲檔案對應的目錄中

      scrapy runspider spiderName.py

    • 向調度器的佇列中扔入一個起始的url

      redisl-cli

      lpush redis_key的屬性值(被共享的調度器佇列名稱) 起始的網址

示例代碼

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fbsPro.items import FbsproItem


class FbsSpider(RedisCrawlSpider):
    name = 'fbs'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    redis_key = 'sunQueue'  # 可被共享的調度器佇列的名稱
    rules = (
        Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
        for li in li_list:
            title = li.xpath('./span[3]/a/text()').extract_first()
            item = FbsproItem()
            item['title'] = title
            yield item

增量式

  • 監測網站資料更新情況,以便于爬取到最新更新的網站

  • 核心:去重

  • 記錄儀:

    特性:永久性存盤(redis中的set)

    爬取過的資料對應的url

    • 可以以明文的形式存盤(url資料長度較短)

    • 記錄的資料對其生成一個資料指紋(url資料長度比較長)

      資料指紋就是該組資料的唯一標識

示例代碼

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
from zlsPro.items import ZlsproItem


class ZlsSpider(CrawlSpider):
    name = 'zls'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
    # 創建redis的鏈接物件
    conn = Redis(host="127.0.0.1", port=6379, password='redis的密碼,沒有就不寫')
    rules = (
        Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=False),
    )

    def parse_item(self, response):
        # 決議出標題和詳情頁的url(詳情頁的url需要存盤到記錄表中)
        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
        for li in li_list:
            title = li.xpath('./span[3]/a/text()').extract_first()
            detail_url = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first()
            item = ZlsproItem()
            item['title'] = title
            # 將進行請求發送的詳情頁的url去記錄表中進行查看
            ex = self.conn.sadd('urls', detail_url)
            if ex == 1:
                print('資料已更新,可爬取')
                yield scrapy.Request(url=detail_url, callback=self.parse_deatil, meta={'item': item})
            else:
                print('資料未更新,不可爬')

    def parse_deatil(self, response):
        item = response.meta['item']
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
        item['content'] = content
        yield item

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/165069.html

標籤:Python

上一篇:Python爬蟲連載17-ItemPipeLine、中間件

下一篇:Python基礎一

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more