1. 程式人生 > 實用技巧 >爬蟲入門三 scrapy

爬蟲入門三 scrapy


title: 爬蟲入門三 scrapy
date: 2020-03-14 14:49:00
categories: python
tags: crawler

scrapy框架入門

1 scrapy簡介

爬蟲框架是實現爬蟲功能的一個軟體結構和功能元件集合。
官方網站:https://scrapy.org/
Scrapy 0.24 文件: http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html

Requests vs Scrapy
相同點:兩者都可以進行頁面請求和爬取,Python爬蟲的兩個重要技術路線 兩者可用性都好,文件豐富,入門簡單 兩者都沒有處理js、提交表單、應對驗證碼等功能(可擴充套件)

Requests
頁面級爬蟲 功能庫 併發性考慮不足,效能較差 重點在於頁面下載 定製靈活 上手十分簡單
Scrapy
網站級爬蟲 框架 併發性好,效能較高 重點在於爬蟲結構 一般定製靈活,深度定製困難 入門稍難

2 scrapy 框架,資料流,資料型別

2.1 scrapy框架(5+2)

Spider
(1) 解析Downloader返回的響應(Response) (2) 產生爬取項(scraped item) (3) 產生額外的爬取請求(Request)
Engine
(1)控制所有模組之間的資料流 (2)根據條件觸發事件
Downloader
根據請求下載網頁
Scheduler
對所有爬取請求進行排程管理
Item Pipelines
(1) 以流水線方式處理Spider產生的爬取項 (2) 由一組操作順序組成,類似流水線 (3) 可能操作包括:清理、檢驗和查重爬取項中 的HTML資料、將資料儲存到資料庫
Downloader Middleware
目的:實施Engine、 Scheduler和Downloader    之間進行使用者可配置的控制 功能:修改、丟棄、新增請求或響應
Spider Middleware
目的:對請求和爬取項的再處理 功能:修改、丟棄、新增請求或爬取項

2.2 scrapy資料流


1.Engine從Spider處獲得爬取請求(Request)
2.Engine將爬取請求轉發給Scheduler,用於排程
3.Engine從Scheduler處獲得下一個要爬取的請求
4.Engine將爬取請求通過中介軟體傳送給Downloader
5.爬取網頁後,Downloader形成響應(Response) 通過中介軟體發給Engine
6.Engine將收到的響應通過中介軟體傳送給Spider處理
7.Spider處理響應後產生爬取項(scraped Item) 和新的爬取請求(Requests)給Engine
8.Engine將爬取項傳送給Item Pipeline(框架出口)
9.Engine將爬取請求傳送給Scheduler

2.3 scrapy資料型別


Request類
Request物件表示一個HTTP請求,
由Spider生成,由Downloader執行

Response類
Response物件表示一個HTTP響應,
由Downloader生成,由Spider處理

Item類
Item物件表示一個從HTML頁面中提取的資訊內容
由Spider生成,由Item Pipeline處理

Item類似字典型別,可以按照字典型別操作

3 scrapy安裝與使用

3.1 scrapy安裝

3.1.1 anoconda安裝

conda install scrapy
出現問題/慢,可以新增清華源

3.1.2 pip安裝 不推薦

Python 3.5/3.6 下安裝scrapy方法:
(1)安裝lxml: pip install lxml
(2)下載對應版本的Twisted
 http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
pip install D:\Twisted-16.4.1-cp35-cp35m-win_amd64.whl
(下載好的twisted模組的whl檔案路徑)
(3)安裝scrapy:pip install scrapy
(4)安裝關聯模組pypiwin32:pip install pypiwin32

3.2 scrapy常用連結

3.3 示例

3.3.1 建立專案

命令列輸入:
scrapy startproject tutorial

tutorial/
    scrapy.cfg
    tutorial/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...

scrapy.cfg: 專案的配置檔案
tutorial/: 該專案的python模組。之後您將在此加入程式碼

tutorial/items.py: 專案中的item檔案.
tutorial/pipelines.py: 專案中的pipelines檔案.
tutorial/settings.py: 專案的設定檔案.
tutorial/spiders/: 放置spider程式碼的目錄.

3.3.2 定義item

Item 是儲存爬取到的資料的容器;其使用方法和python字典類似,並且提供了額外保護機制來避免拼寫錯誤導致的未定義欄位錯誤。
編輯 tutorial 目錄中的 items.py 檔案:

import scrapy

class DmozItem(scrapy.Item):
    title = scrapy.Field()
    link = scrapy.Field()
    desc = scrapy.Field() 

Field 物件僅僅是內建的 dict 類的一個別名,並沒有提供額外的方法或者屬性。換句話說, Field 物件完完全全就是Python字典(dict)。被用來基於類屬性(class attribute)的方法來支援 item宣告語法 。

3.2.3 編寫spider

Spider是使用者編寫用於從單個網站(或者一些網站)爬取資料的類。其包含了一個用於下載的初始URL,如何跟進網頁中的連結以及如何分析頁面中的內容, 提取生成 item 的方法。
為了建立一個Spider,必須繼承 scrapy.Spider 類, 且定義以下三個屬性:

name: 用於區別Spider。 該名字必須是唯一的,不可以為不同的Spider設定相同的名字。
start_urls: 包含了Spider在啟動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 後續的URL則從初始的URL獲取到的資料中提取。
parse() 是spider的一個方法。 被呼叫時,每個初始URL完成下載後生成的 Response 物件將會作為唯一的引數傳遞給該函式。 該方法負責解析返回的資料(response data),提取資料(生成item)以及生成需要進一步處理的URL的 Request 物件。

在專案中生成 spider 檔案的兩種方法:

命令列輸入 Scrapy genspider domain domain.com

tutorial/spiders/目錄下建立domain.py

#爬取網頁的內容
# -*- coding: utf-8 -*-
import scrapy

class W3schoolSpider(scrapy.Spider):
    name = 'w3school'
    allowed_domains = ['w3school.com.cn']
    start_urls = ['http://www.w3school.com.cn/cssref/css_selectors.asp',
                  'http://www.w3school.com.cn/xpath/']

    def parse(self, response):
        filename = response.url.split("/")[-2]
        with open(filename, 'wb') as f:
            f.write(response.body)
        pass

3.2.4 執行spider

進入專案的根目錄,執行下列命令啟動spider:
scrapy crawl w3school

log包含定義在 start_urls 的初始URL,並且與spider中是一一對應的。在log中可以看到其沒有指向其他頁面( (referer:None) )。檢視當前目錄,兩個包含url所對應的內容的檔案被建立了: cssref, xpath, 正如我們的 parse 方法裡做的一樣。

Scrapy為Spider的 start_urls 屬性中的每個URL建立了 scrapy.Request 物件,並將 parse 方法作為回撥函式(callback)賦值給了Request。Request物件經過排程,執行生成 scrapy.http.Response 物件並送回給spider parse() 方法。

3.2.5 提取item Xpath

Scrapy爬蟲支援多種HTML資訊提取方法:
Beautiful Soup
Lxml
Re
Xpath
CSS

3.2.6 Xpath selector

XPath 使用路徑表示式在 XML 文件中選取節點。節點是通過沿著路徑或者 step 來選取的。
常見的路徑表示式如下:

謂語(Predicates)用來查詢某個特定的節點或者包含某個指定的值的節點,嵌在方括號[]中。

4 pycharm+anoconda+scrapy

在anaconda按照scrapy,然後開啟pycharm,新增anaconda到interpreter。

然後在pycharm的命令列(下面的terminal)輸入scrapy startproject xxx [mulu]

然後file,open剛剛建立好的目錄,就可以使用模板了

5 建議

第3部分的示例內容比較簡略,比如說spider中就沒有講parse的定義引數是因為downloader返回了reponse物件。

比如scrapy crwal w3school 中w3school是domin.py中的name。

詳細內容見 https://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html

6 示例 豆瓣電影top250

專案:scrapy startproject douban250

6.1 item.py

import scrapy

class DoubanMovieItem(scrapy.Item):
    # 排名
    ranking = scrapy.Field()
    # 電影名稱
    movie_name = scrapy.Field()
    # 評分
    score = scrapy.Field()
    # 評論人數
    score_num = scrapy.Field()

6.2 DoubanMovieTop250.py (spider)

from scrapy import Request
from scrapy.spiders import Spider
from douban250.items import DoubanMovieItem

class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        item = DoubanMovieItem()
        movies = response.xpath('//ol[@class="grid_view"]/li')
        for movie in movies:
            item['ranking'] = movie.xpath('.//div[@class="pic"]/em/text()').extract()[0]
            item['movie_name'] = movie.xpath('.//div[@class="hd"]/a/span[1]/text()').extract()[0]
            item['score'] = movie.xpath('.//div[@class="star"]/span[@class="rating_num"]/text()').extract()[0]
            item['score_num'] = movie.xpath('.//div[@class="star"]/span/text()').re(r'(\d+)人評價')[0]
            yield item

.xpath() 方法返回一個類 SelectorList 的例項, 它是一個新選擇器的列表。
.re() 方法用來通過正則表示式來提取資料,返回unicode字串的列表。
.extract() 方法序列化並將匹配到的節點返回一個unicode字串列表。

執行後報錯403,豆瓣對爬蟲有限制,需要修改訪問的user-agent。
將 start_urls = [‘https://movie.douban.com/top250’] 改為:

class DoubanMovieTop250Spider(Spider):
    name = 'douban_movie_top250'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_requests(self):
        url = 'https://movie.douban.com/top250'
        yield Request(url, headers=self.headers)

start_urls URL列表。當沒有制定特定的URL時,spider將從該列表中開始進行爬取。因此,第一個被獲取到的頁面的URL將是該列表之一。 後續的URL將會從獲取到的資料中提取。

start_requests() 該方法必須返回一個可迭代物件(iterable)。該物件包含了spider用於爬取的第一個Request。當spider啟動爬取並且未指定URL時,該方法被呼叫。當指定了URL時,make_requests_from_url() 將被呼叫來建立Request物件。該方法僅僅會被Scrapy呼叫一次,預設實現是使用 start_urls 的url生成Request。

parse(response) 當response沒有指定回撥函式時,該方法是Scrapy處理下載的response的預設方法。

6.3 自動翻頁 加到parse後

 next_url = response.xpath('//span[@class="next"]/a/@href').extract()
        if next_url:
            next_url = 'https://movie.douban.com/top250' + next_url[0]
            yield Request(next_url, headers=self.headers)

兩種方法:
從當前頁面中提取
根據URL的變化規律構造所有頁面地址(適用於頁面的下一頁地址為JS載入)

6.4 執行

執行爬蟲程式
scrapy crawl douban_movie_top250 -o douban.csv
在專案所在目錄下執行爬蟲,並將結果輸出到.csv檔案。同樣支援其他序列化格式:JSON、JSON lines、XML,和多種儲存方式:本地檔案系統路徑,ftp,Amazon S3等

報錯

KeyError: 'Spider not found: douban_movie_top250'

原因是縮排問題。parse和start_requests都屬於DoubanMovieTop250Spider類。

然後是settings.py出錯,改為

BOT_NAME = 'douban250'

SPIDER_MODULES = ['douban250.spiders']
NEWSPIDER_MODULE = 'douban250.spiders'

然後就可以執行

6.5 輸出的csv的編碼問題

但是開啟csv檔案

有很多問題。
參考 https://blog.csdn.net/dayun555/article/details/79416447

utf-8:全球通用編碼
ascii:能儲存字母/數字/符號,美國專用
gbk|gb2312|gb18030:能夠儲存漢字

要生成經編碼後的csv型別檔案
cmdline.execute(['scrapy', 'crawl', '爬蟲檔名稱', '-o', '檔名.csv', '-s', 'FEED_EXPORT_ENCODING="gb18030"'])
例如:cmdline.execute(['scrapy', 'crawl', 'ivsky', '-o', 'img.csv', '-s', 'FEED_EXPORT_ENCODING="gb18030"'])

要生成經編碼後的json型別檔案
cmdline.execute(['scrapy', 'crawl', '爬蟲檔名稱', '-o', '檔名.json', '-s', 'FEED_EXPORT_ENCODING=utf-8'])

例如:cmdline.execute(['scrapy', 'crawl', 'ivsky', '-o', 'img.json', '-s', 'FEED_EXPORT_ENCODING=utf-8'])

修改settings,新增

FEED_EXPORT_ENCODING = "gb18030"  # gbk不行

然後再執行 scrapy crawl douban_movie_top250 -o douban.csv,內容就正常了

6.6 scrapy.cmdline.execute

有這個模組可以不用手動輸入。

新建auto.py

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

# 方式一:注意execute的引數型別為一個列表
cmdline.execute('scrapy crawl spidername'.split())

# 方式二:注意execute的引數型別為一個列表
cmdline.execute(['scrapy', 'crawl', 'spidername'])

cmdline.execute(['scrapy', 'crawl', '爬蟲檔名稱', '-o', '檔名.json', '-s', 'FEED_EXPORT_ENCODING=utf-8'])

然後執行該檔案即可。

這裡

from scrapy import cmdline

cmdline.execute(['scrapy', 'crawl', 'douban_movie_top250', '-o', 'doubanauto.csv'])

6.7 擴充套件。settings.py

可以選擇性的新增。

ROBOTSTXT_OBEY = True		是否遵守robots.txt
CONCURRENT_REQUESTS = 16	開啟執行緒數量,預設16
AUTOTHROTTLE_START_DELAY = 3	開始下載時限速並延遲時間
AUTOTHROTTLE_MAX_DELAY = 60	高併發請求時最大延遲時間

HTTPCACHE_ENABLED = True	
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = ‘httpcache’
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = ‘scrapy.extensions.httpcache.FilesystemCacheStorage’
以上幾個引數對本地快取進行配置,如果開啟本地快取會優先讀取本地快取,從而加快爬取速度

USER_AGENT = ‘projectname (+http://www.yourdomain.com)’
對requests的請求頭進行配置,比如可以修改為‘Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36’同樣可以避免伺服器返回403

6.8 擴充套件。pipeline.py

可以選擇性的新增。

當Item在Spider中被收集之後,它將會被傳遞到Item Pipeline,一些元件會按照一定的順序執行對Item的處理。如果僅僅想要儲存item,則不需要實現的pipeline。
item pipeline的一些典型應用有:
清理HTML資料
驗證爬取的資料(檢查item包含某些欄位)
查重(並丟棄)
將爬取結果儲存到資料庫中

#去重過濾,丟棄那些已經被處理過的item。spider返回的多個item中包含有相同的id:
from scrapy.exceptions import DropItem

class DuplicatesPipeline(object):

    def __init__(self):
        self.ids_seen = set()

    def process_item(self, item, spider):
        if item['id'] in self.ids_seen:
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.ids_seen.add(item['id'])
            return item