python爬蟲之Scrapy框架

Scrapy是用python實現的一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。使用Twisted高效異步網絡框架來處理網絡通訊。css

Scrapy架構:html

 

ScrapyEngine:引擎。負責控制數據流在系統中全部組件中流動,並在相應動做發生時觸發事件。 此組件至關於爬蟲的「大腦」,是 整個爬蟲的調度中心。 python

Schedule:調度器。接收從引擎發過來的requests,並將他們入隊。初始爬取url和後續在頁面裏爬到的待爬取url放入調度器中,等待被爬取。調度器會自動去掉重複的url。正則表達式

Downloader:下載器。負責獲取頁面數據,並提供給引擎,然後提供給spider。json

 

Spider:爬蟲。用戶編些用於分析response並提取item和額外跟進的url。將額外跟進的url提交給ScrapyEngine,加入到Schedule中。將每一個spider負責處理一個特定(或 一些)網站。 windows

ItemPipeline:負責處理被spider提取出來的item。當頁面被爬蟲解析所需的數據存入Item後,將被髮送到Pipeline,並通過設置好次序服務器

DownloaderMiddlewares:下載中間件。是在引擎和下載器之間的特定鉤子(specific hook),處理它們之間的請求(request)和響應(response)。提供了一個簡單的機制,經過插入自定義代碼來擴展Scrapy功能。經過設置DownloaderMiddlewares來實現爬蟲自動更換user-agent,IP等。cookie

SpiderMiddlewares:Spider中間件。是在引擎和Spider之間的特定鉤子(specific hook),處理spider的輸入(response)和輸出(items或requests)。提供了一樣簡單機制,經過插入自定義代碼來擴展Scrapy功能。網絡

 

數據流:架構

1.ScrapyEngine打開一個網站,找處處理該網站的Spider,並向該Spider請求第一個(批)要爬取的url(s);

2.ScrapyEngine向調度器請求第一個要爬取的url,並加入到Schedule做爲請求以備調度;

3.ScrapyEngine向調度器請求下一個要爬取的url;

4.Schedule返回下一個要爬取的url給ScrapyEngine,ScrapyEngine經過DownloaderMiddlewares將url轉發給Downloader;

5.頁面下載完畢,Downloader生成一個頁面的Response,經過DownloaderMiddlewares發送給ScrapyEngine;

6.ScrapyEngine從Downloader中接收到Response,經過SpiderMiddlewares發送給Spider處理;

7.Spider處理Response並返回提取到的Item以及新的Request給ScrapyEngine;

8.ScrapyEngine將Spider返回的Item交給ItemPipeline,將Spider返回的Request交給Schedule進行從第二步開始的重複操做,直到調度器中沒有待處理的Request,ScrapyEngine關閉。

 

安裝scrapy:

1.安裝wheel支持:
$ pip install wheel

2.安裝scrapy框架:

$ pip install scrapy

3.window下,爲了不windows編譯安裝twisted依賴,安裝下面的二進制包 

$ pip install Twisted-18.4.0-cp35-cp35m-win_amd64.whl

 

scrapy項目結構:
在某路徑下建立scrapy項目: $ scrapy startproject my_project

會產生如下目錄和文件:

 

外部的first目錄:整個項目目錄
scrapy.cfg:必須有的重要的項目的配置文件

內部的first目錄:整個項目的全局目錄

item.py:定義Item類,從scrapy.Item繼承,裏面定義scrapy.Field類

pipelines.py:處理爬取的數據流向。重要的是process_item()方法

first目錄下的__init__.py:做爲包文件必須有的文件

spiders目錄下的__init__.py:也是必須有。在這裏能夠寫爬蟲類或爬蟲子模塊

settings.py:

BOT_NAME  # 爬蟲名

ROBOTSTXT_OBEY = True  # 遵照robots協議

USER_AGENT=''  # 指定爬取時使用。必定要更改user-agent,不然訪問會報403錯誤

CONCURRENT_REQUEST = 16  # 默認16個並行

DOWNLOAD_DELAY = 3  # 下載延時

COOKIES_ENABLED = False  # 缺省是啓用。通常須要登陸時才須要開啓cookie

DEFAULT_REQUEST_HEADERS = {}  # 默認請求頭,須要時填寫

SPIDER_MIDDLEWARES  # 爬蟲中間件

DOWNLOADER_MIDDLEWARES  # 下載中間件

'first.middlewares.FirstDownloaderMiddleware': 543  # 543優先級越小越高

'firstscrapy.pipelines.FirstscrapyPipeline': 300  # item交給哪個管道處理,300優先級越小越高

  

豆瓣書評爬取:

建立爬蟲代碼模版

命令:scrapy genspider -t basic book douban.com  # book是爬蟲名字;douban.com是要爬取的url的域名

模板以下:

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


class BookSpider(scrapy.Spider):
    name = 'book'
    allowed_domains = ['douban.com']
    start_urls = ['http://douban.com/']

    def parse(self, response):
        pass

 此時就已經成功建立一個名爲‘book’的爬蟲,能夠經過命令scrapy list查看。

 

response是服務器端HTTP響應,它是scrapy.http.response.html.HtmlResponse類。由此,修改代碼以下 :

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse

class BookSpider(scrapy.Spider):
    name = 'book'  # 爬蟲名
    allowed_domains = ['douban.com']  # 域名。爬蟲爬取範圍
    start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T']  # 起始url,從第一頁開始爬取

    # 下載器獲取WebServer的response,parse就是解析響應response的內容
    def parse(self, response: HtmlResponse):  # 如何解析html;返回一個可迭代對象:利用yiled
        print(type(response))  # scrapy.http.response.html.HtmlResponse
        print(type(response.text))  # str
        print(type(response.body))  # bytes
        print(response.encoding)  # utf-8
        # 將網頁內容寫入book.html文件內
        with open('/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html', 'w', encoding='utf-8') as f:
            f.write(response.text)
            f.flush()
        except Exception as e:
            print(e)

 

爬蟲得到的內容response對象,能夠使用解析庫來解析。scrapy包裝了lxml,父類TextResponse類也提供了xpath方法和css方法,能夠混合使用這兩套接口解析HTML。解析html頁面內容的示例代碼以下:

# -*- coding: utf-8 -*-
from scrapy.http.response.html import HtmlResponse

response = HtmlResponse('file:/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html', encoding='utf-8')
with open('/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html', encoding='utf8') as f:
    response._set_body(f.read().encode())  # _set_body方法將其放入response對象裏;須要傳入的參數對象是bytes,因此encode()
subjects = response.css('li.subject-item')
for subject in subjects:
    # 提取書籍的網頁連接
    href = subject.xpath('.//h2').css('a::attr(href)').extract()
    print('href:', href[0])
    # 使用正則表達式,選取評分是9分以上的書籍
    rate = subject.xpath('.//span[@class="rating_nums"]/text()').re(r'^9.*')
    # rate = subject.css('span.rating_nums::text').re(r'^9\..*')  # 第二種表達方式
    if rate:
        print(rate[0])

 

item封裝數據:

# first/item.py

import scrapy

class Test1ProItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()  # 存放書籍標題的字段
    rate = scrapy.Field()  # 存放書籍評分的字段

 

 

# first/first/spiders/book.py

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import FirstItem  # 從上一層的items.py文件裏導入

class BookSpider(scrapy.Spider):
    name = 'book'  # 爬蟲名
    allowed_domains = ['douban.com']  # 爬蟲爬取範圍
    start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T']  # 起始url
    custom_settings = {'file_name': '/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json'}  # 通常設置參數
 
    # 下載器獲取WebServer的response,parse解析響應的內容;輸出items和requests
    def parse(self, response: HtmlResponse):  # 如何解析html;返回一個可迭代對象:利用yiled
        subjects = response.xpath('//li[@class="subject-item"]')
        items = []  # 若是用items=[],最後函數要return items
        for subject in subjects:
            item = FirstItem()  # 聲明一個item,至關於一個字典,存放要爬取的數據

            title = subject.xpath('.//h2/a/text()').extract()
            item['title'] = title[0].strip()
            rate = subject.css('span.rating_nums::text').extract()
            item['rate'] = rate[0].strip()

            items.append(item)

        with open('book.json', 'w', encoding='utf8') as f:
            for item in items:
                f.write('{} {}\n'.format(item['title'], item['rate']))

        return items

 

pipeline處理

將book.py中BookSpider改爲生成器,只須要把return items改形成yield item,即由產生一個列表變成yield一個個item。腳手架幫咱們建立了一個pipelines.py文件和一個類。 

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'first.pipelines.FirstPipeline': 300,
}

整數300表示優先級,越小越高。取值範圍爲0-1000。 

 pipeline.py裏經常使用的方法:

process_item(self, item, spider)  # item表示爬取的一個個數據,spider表示item的爬取者,每個item處理都得調用。返回一個item對象,或者拋出DropItem異常,被丟棄的item對象將不會被pipeline組件處理;

open_spider(self, spider)  # spider表示被開啓的spider,調用一次

close_spider(self, spider)  # spider表示被關閉的spider,調用一次

__init__(self)  # spider建立實例時調用一次

 

將爬取的數據經過pipeline寫入到json文件中,代碼以下:

# first/spiders/book.py

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import Test1ProItem

class BookSpider(scrapy.Spider):
    name = 'book'
    allowed_domains = ['douban.com']
    start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T']
    custom_settings = {'file_name': '/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json'}  # spider上自定義配置信息

    def parse(self, response: HtmlResponse):  # 如何解析html;返回一個可迭代對象:利用yiled
        subjects = response.xpath('//li[@class="subject-item"]')
        for subject in subjects:
            item =FirstProItem()
            title = subject.xpath('.//h2/a/text()').extract()
            item['title'] = title[0].strip()
            rate = subject.css('span.rating_nums::text').extract()
            item['rate'] = rate[0].strip()
            yield item  # 返回一個可迭代對象生成器

 

# first/pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import json
from scrapy import Spider

class Test1ProPipeline(object):
    def __int__(self):
        print('~~~~~~~~~~init~~~~~~~~~~')

    # 每個item都會執行一次
    def process_item(self, item, spider:Spider):
        print('++++++++++')
        print(item)
        self.file.write('{},\n'.format(json.dumps(dict(item))))
        return item

    # 全部過程在起始的時候執行一次
    def open_spider(self, spider):
        print('==========open spider {}=========='.format(spider))
        # file_name = '/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json'
        file_name = spider.settings['file_name']
        self.file = open(file_name, 'w', encoding='utf-8')
        self.file.write('[\n')

    # 全部過程結束的時候執行一次
    def close_spider(self, spider):
        print('==========close spider {}=========='.format(spider))
        self.file.write(']')
        self.file.close()
相關文章
相關標籤/搜索