1. 程式人生 > 實用技巧 >scrapy框架(一)

scrapy框架(一)

scrapy框架的介紹

scrapy是一個開源和協作的框架。是一個快速、簡單、並且可擴充套件的方法。Scrapy使用了非同步網路框架來處理網路通訊,可以獲得較快的下載速度,因此,我們不需要去自己實現非同步框架。並且,Scrapy包含了各種中介軟體介面,可以靈活的完成各種需求。所以我們只需要定製開發幾個模組就可以輕鬆的實現一個爬蟲,用來抓取網頁上的各種內容。

scrapy爬蟲的優點:

<1>內建的css選擇器和xpath表示式;
<2>擴充套件性強,可使用signals和api(中介軟體,外掛,管道)新增自定義功能;
<3>多用於session,cookies,http認證,user-agent,robots.txt,抓取深度限制的中介軟體和外掛
<4>scrapy內建teinet console,可用於debug;
<5>健壯的編碼支援。

scrapy框架的組成

從上圖中,可以看出scrapy框架由7個部分組成,分別是5個元件和2箇中間件。

1.引擎(EGINE

引擎(egine)是整個框架的核心部分,維繫著整個框架的生命。通過它,可以處理整個系統的資料流,並在某些動作時觸發事務。

2.排程器(SCHEDULER

用來接受引擎發過來的請求, 傳入佇列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是連結)的優先佇列, 由它來決定下一個要抓取的網址是什麼, 同時比較強大的功能就是:可以對以前已經抓取過得連結進去重。

3.下載器(DOWNLOADER

用於下載網頁內容, 並將網頁內容返回給Spider(Scrapy下載器是建立在twisted這個高效的非同步模型上的),其實是將抓取的內容返回給引擎,引擎然後將網站的內容返回給spider。spider將內容進行解析。提取有用的資料或者進行下一步的抓取,或者將資料結構化給pipeline。

4.爬蟲(SPIDERS

SPIDERS是開發人員自己定義的類,用來解析responses,並且提取items, 獲取重新發送新請求

5.專案管道(TIEM PIPLINES

在items被提取後負責處理它們,主要包括清理、驗證,持久化(比如存到資料庫中)等操作

6.下載中介軟體(DOWNLOADER MIDDLEWARES

位於Scrapy引擎和下載器之間,主要是用來處理從EGINE傳到DOWNLOADER的請求request,已經從DOWNLOADER傳到EGINE的響應response。

process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);

change received response before passing it to a spider;

send a new Request instead of passing received response to a spider;

pass response to a spider without fetching a web page;

silently drop some requests.

7.爬蟲中介軟體(Spider middlewares

位於EGINESPIDERS之間,主要工作是處理SPIDERS的輸入(responses)和輸出(request)

官網連結:https://docs.scrapy.org/en/latest/topics/architecture.html

Srapy框架的安裝

windows平臺安裝

step1  pip install wheel
step2  pip install lxml
step3  pip install pyopenssl
step4  pip install pywin32
step5  pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
step6  pip install scrapy

建立Scrapy專案

# scrapy可執行檔案的路徑
D:\Python\Python38\Scripts\scrapy.exe
 
# 建立scrapy專案
E:\>scrapy startproject firstscrapy
    
# 建立爬蟲
scrapy genspider 爬蟲名  爬蟲地址
E:\firstscrapy\firstscrapy\spiders>scrapy genspider chouti dig.chouti.com

# 執行爬蟲
# 帶執行日誌
scrapy crawl chouti
# 不帶執行日誌
scrapy crawl chouti --nolog

# 支援右鍵執行爬蟲
<1>建立可執行檔案run.py
<2>在run.py中輸入:
    from scrapy.cmdline import execute
    execute(['scrapy','crawl','chouti','--nolog'])  # 這裡的‘chouti’是類中定義的name

抽屜新熱網https://dig.chouti.com/

scrapy專案的目錄結構

firstscrapy                # 專案名稱
    firstscrapy            # 包
        spiders            # 所有爬蟲的包
            __init__.py
            chouti.py
        __init__.py
        items.py           # 一個一個的類
        middlewares.py     # 中介軟體(爬蟲,下載中介軟體都寫在這)
        pipelines.py       # 持久化相關寫在這(items.py中類的物件
        run.py             # 執行爬蟲
        settings.py        # 配置檔案
    scrapy.cfg             # 上線相關

settings配置檔案

1 預設情況,scrapy會去遵循爬蟲協議
2 修改配置檔案引數,強行爬取,不遵循協議
	-ROBOTSTXT_OBEY = False
3 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
4 LOG_LEVEL='ERROR'

使用scrapy框架爬取抽屜新聞

使用解析的三種方式

使用第三方解析:

from bs4 import BeautifulSoup

def parse(self, response):
    print(response.text)
    # 解析資料(第一種方案,自己解析,bs4,lxml)
    soup=BeautifulSoup(response.text,'lxml')
    divs=soup.find_all(class_='link-title')
    for div in divs:
        print(div.text)

繼續爬取其他網址:

def parse(self, response):
    # 以後解析都在這
    print(response.status)
    # 假設解析出一個網址(繼續爬取)
    # don_filter=True  不過濾除了allowed_domains之外的URL
    return Request('https://www.baidu.com/',dont_filter=True)

使用自帶的解析方式:

# 取文字
-css選擇器
	response.css('.link-title::text').extract()  #取多個,列表
-xpath選擇器
	response.xpath('//a[contains(@class,"link-title")]/text()').extract()
    
# 取屬性
    response.css('.link-title::attr(href)').extract_first() # 取第一個
    response.xpath('//a[contains(@class,"link-title")]'/@href).extra_first()

解析出所有的標題和圖片地址

使用xpath選擇器

def parse(self, response, **kwargs):
    news_list = [
        {
            'title':div.xpath('.//a[contains(@class,"link-title")]/text()').extract_first(),
            'img_url':div.xpath('.//img[contains(@class,"image-scale")]/@src').extract_first(),
            # 'title':div.css('.link-title::text').extract_first(),
            # 'img_url':div.css('.image-scale::attr(src)').extract_first(),
        }
        for div in response.xpath('//div[contains(@class,"link-item")]')
    ]
    print(len(news_list))
    for news in news_list:
        print(news)

使用css選擇器

        news_list = [
            {
                # 'title':div.xpath('.//a[contains(@class,"link-title")]/text()').extract_first(),
                # 'img_url':div.xpath('.//img[contains(@class,"image-scale")]/@src').extract_first(),
                'title':div.css('.link-title::text').extract_first(),
                'img_url':div.css('.image-scale::attr(src)').extract_first(),
            }
            for div in response.xpath('//div[contains(@class,"link-item")]')
        ]
        print(len(news_list))
        for news in news_list:
            print(news)

scrapy持久化

方案一:parser函式必須返回列表套字典的形式(瞭解)

儲存為csv檔案

def parse(self, response, **kwargs):
    news_list = []
        for div in response.xpath('//div[contains(@class,"link-item")]'):
            news_list.append(
                {
                    'title':div.xpath('.//a[contains(@class,"link-title")]/text()').extract_first(),
                    'img_url':div.xpath('.//img[contains(@class,"image-scale")]/@src').extract_first()
                }
            )
        return news_list

在終端中執行以下命令:

scrapy crawl drawer -o drawer.csv -s FEED_EXPORT_ENCODING=GBK

# 也可以在settings.py中指定編碼
FEED_EXPORT_ENCODING='GBK'

儲存為json格式檔案

scrapy crawl drawer -o drawer.json

# 開啟檔案顯示的是Unicode編碼,如果想顯示為中文,需要設定下:-s FEED_EXPORT_ENCODING=UTF-8

方案二:高階,pipline item儲存(mysqlredisfile

Items.py中寫一個類

import scrapy


class DrawerItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    url = scrapy.Field()
    img_url = scrapy.Field()

spinder中匯入,例項化,把資料放進去

import scrapy

from firstscrapy.items import DrawerItem

class DrawerSpider(scrapy.Spider):
    name = 'drawer'
    allowed_domains = ['dig.chouti.com']
    start_urls = ['http://dig.chouti.com/']
    def parse(self, response, **kwargs):    
        for div in response.xpath('//div[contains(@class,"link-item")]'):
                item = DrawerItem()
                title = div.xpath('.//a[contains(@class,"link-title")]/text()').extract_first()
                url = div.xpath('.//a[contains(@class,"link-title")]/@href').extract_first()
                img_url = div.xpath('.//img[contains(@class,"image-scale")]/@src').extract_first()
                item['title'] = title
                item['url'] = url
                item['img_url'] = img_url
                yield item

settings中配置(數字越小,級別越高

ITEM_PIPELINES = {
   'firstscrapy.pipelines.DrawerFilePipeline': 300,
   'firstscrapy.pipelines.DrawerMysqlPipeline': 305,
   'firstscrapy.pipelines.DrawerRedisPipeline': 310,
}

pipelines.py中寫DrawerFilePipeline

class DrawerFilePipeline:
    def open_spider(self,spider):
        self.file = open('drawer.txt',mode='w',encoding='utf-8')

    def process_item(self, item, spider):
        title = item['title']
        url = item['url']
        img_url = item['img_url'] or 'None'
        self.file.write(title+'\n'+url+'\n'+img_url+'\n')
        return item

    def close_spider(self,spider):
        self.file.close()
        
"""
-open_spider(開始的時候)
-close_spider(結束的時候)
-process_item(在這持久化)
"""