scrapy爬蟲框架

簡介

​ Scrapy一個開源和協做的框架,其最初是爲了頁面抓取所設計的,使用它能夠以快速、簡單、可擴展的方式從網站中提取所需的數據。但目前Scrapy的用途十分普遍,可用於如數據挖掘、監測和自動化測試等領域,也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。css

​ Scrapy 是基於twisted框架開發而來,twisted是一個流行的事件驅動的python網絡框架。所以Scrapy使用了一種非阻塞(又名異步)的代碼來實現併發。總體架構大體以下html

1036857-20171109221422778-1731419400

執行流程:node

1.引擎從spider獲取初始爬行請求。
2.引擎在調度程序中調度請求,並請求下一個要爬行的請求。
3.調度程序將下一個請求返回到引擎。
4.引擎將請求發送到下載器,並經過下載器中間件(請參見process_request())。
5.一旦頁面完成下載,下載器將生成響應(使用該頁面),並將其發送到引擎,並經過下載器中間軟件(請參見process_response())。
6.引擎接收下載器的響應並將其發送給spider進行處理,並經過spider中間件進行處理(請參見process_spider_input())。
7.spider處理響應,並經過spider中間件(請參見process_spider_output())向引擎返回刮掉的項目和新請求(後續)。
8.引擎將已處理的項目發送到項目管道,而後將已處理的請求發送到計劃程序,並請求可能的下一個請求進行爬網。
9.該過程重複(從步驟1開始),直到調度程序再也不發出請求。python

各組件以及做用

  1. 引擎(EGINE)git

    引擎負責控制系統全部組件之間的數據流,並在某些動做發生時觸發事件。有關詳細信息,請參見上面的數據流部分。github

  2. 調度器(SCHEDULER)
    用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL的優先級隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址web

  3. 下載器(DOWLOADER)
    用於下載網頁內容, 並將網頁內容返回給EGINE,下載器是創建在twisted這個高效的異步模型上的ajax

  4. 爬蟲(SPIDERS)
    SPIDERS是開發人員自定義的類,用來解析responses,而且提取items,或者發送新的請求sql

  5. 項目管道(ITEM PIPLINES)
    在items被提取後負責處理它們,主要包括清理、驗證、持久化(好比存到數據庫)等操做shell

  6. 下載器中間件(Downloader Middlewares)

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

  7. 爬蟲中間件(Spider Middlewares)
    位於EGINE和SPIDERS之間,主要工做是處理SPIDERS的輸入(即responses)和輸出(即requests

安裝

#Windows平臺
    一、pip3 install wheel #安裝後,便支持經過wheel文件安裝軟件,wheel文件官網:https://www.lfd.uci.edu/~gohlke/pythonlibs
    三、pip3 install lxml
    四、pip3 install pyopenssl
    五、下載並安裝pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
    六、下載twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    七、執行pip3 install 下載目錄\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    八、pip3 install scrapy
  
#Linux平臺
    一、pip3 install scrapy

經常使用命令

#1 查看幫助
    scrapy -h
    scrapy <command> -h

#2 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不須要
    Global commands:
        startproject #建立項目
            scrapy startproject amazon
        genspider    #建立爬蟲程序  指定名稱 限制爬取的網址
            scrapy genspider amzon www.amzon.cn
        settings     #若是是在項目目錄下,則獲得的是該項目的配置
            scrapy settings --get BOT_NAME
        runspider    #運行一個獨立的python文件,沒必要建立項目
            scrapy runspider amzon.py
        shell        #scrapy shell url地址  在交互式調試,如選擇器規則正確與否
            scrapy shell www.taobao.com
        fetch        #單純地爬取一個頁面,不打開瀏覽器,能夠拿到請求頭
             scrapy fetch --nolog http://www.baidu.com 不輸出日誌
             scrapy fetch --nolog --header http://www.baidu.com 不輸出日誌  只查看頭信息
        view         #下載完畢後直接彈出瀏覽器,以此能夠分辨出哪些數據是ajax請求
            scrapy view http://www.baidu.com
        version      #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本
            scrapy version -v
            
            
    Project-only commands:
        #必須先切換到對應的目錄才能執行
        crawl        #運行爬蟲,必須建立項目才行,確保配置文件中ROBOTSTXT_OBEY = False
            scrapy crawl amzon
        check        #檢測項目中有無語法錯誤
            scrapy check
        list         #列出項目中所包含的爬蟲名
            scrapy list
        edit         #編輯器,通常不用
        parse        #scrapy parse url地址 --callback 回調函數  #以此能夠驗證咱們的回調函數是否正確
        bench        #scrapy bentch壓力測試
        
#3 官網連接
    https://docs.scrapy.org/en/latest/topics/commands.html

目錄結構

project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           爬蟲1.py
           爬蟲2.py
           爬蟲3.py
文件說明:
scrapy.cfg  項目的主配置信息,用來部署scrapy時使用,爬蟲相關的配置信息在settings.py文件中。
items.py    設置數據存儲模板,用於結構化數據,如:Django的Model
pipelines    數據處理行爲,如:通常結構化的數據持久化
settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等。強調:配置文件的選項必須大寫不然視爲無效,正確寫法USER_AGENT='xxxx'
spiders      爬蟲目錄,如:建立文件,編寫爬蟲規則
注意:通常建立爬蟲文件時,以網站域名命名

import sys,os
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')

在pycharm中運行爬蟲程序

在項目目錄下新建:entrypoint.py

from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'amzon'])

Spiders

簡介

一、Spiders是由一系列類(定義了一個網址或一組網址將被爬取)組成,具體包括如何執行爬取任務而且如何從頁面中提取結構化的數據。

二、換句話說,Spiders是你爲了一個特定的網址或一組網址自定義爬取和解析頁面行爲的地方

spiders的運做過程

#一、生成初始的Requests來爬取第一個URLS,而且標識一個回調函數
第一個請求定義在start_requests()方法內默認從start_urls列表中得到url地址來生成Request請求,默認的回調函數是parse方法。回調函數在下載完成返回response時自動觸發

#二、在回調函數中,解析response而且返回值
返回值能夠4種:
        包含解析數據的字典
        Item對象
        新的Request對象(新的Requests也須要指定一個回調函數)
        或者是可迭代對象(包含Items或Request)

#三、在回調函數中解析頁面內容
一般使用Scrapy自帶的Selectors,但很明顯你也可使用Beutifulsoup,lxml或其餘你愛用啥用啥。

#四、最後,針對返回的Items對象將會被持久化到數據庫
經過Item Pipeline組件存到數據庫:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
或者導出到不一樣的文件(經過Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)

spiders模板類

Spider 基礎的爬蟲 也是咱們呢最經常使用的爬蟲  不會對response 進行任何解析 直接傳給回調函數

SitemapSpider 站點信息爬蟲 對於須要seo優化的網站一般會在網站根目錄下建立 站點地圖文件
 其中列出網站全部連接地址,而後將該文件提交給搜索引擎,搜索引擎收錄後,會查看其中的信息,例如最後跟新時間等,以便於搜索引擎的爬蟲更有效的爬取你的網頁

CrawlSpider
CrawlSpider類定義了一些規則(rule)來提供跟進link的機制,從爬取的網頁中獲取link並繼續爬取。

固定格式爬蟲
CSVFeedSpider  回調函數parse_row包含一個row用於直接提取一行內容
XMLFeedSpider,回調函數parse_node包含一個node表示一個節點

Spider的詳細使用

這是最簡單的spider類,任何其餘的spider類都須要繼承它(包含你本身定義的)。

該類不提供任何特殊的功能,它僅提供了一個默認的start_requests方法默認從start_urls中讀取url地址發送requests請求,而且默認parse做爲回調函數

class AmazonSpider(scrapy.Spider):
    name = 'amazon' 
    allowed_domains = ['www.amazon.cn'] 
    start_urls = ['http://www.amazon.cn/']
    
    custom_settings = {
        'BOT_NAME' : 'Egon_Spider_Amazon',
        'REQUEST_HEADERS' : {
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
          'Accept-Language': 'en',
        }
    }
    def parse(self, response):
        pass
#一、name = 'amazon' 
定義爬蟲名,scrapy會根據該值定位爬蟲程序
因此它必需要有且必須惟一(In Python 2 this must be ASCII only.)

#二、allowed_domains = ['www.amazon.cn'] 
定義容許爬取的域名,若是OffsiteMiddleware啓動(默認就啓動),
那麼不屬於該列表的域名及其子域名都不容許爬取
若是爬取的網址爲:https://www.example.com/1.html,那就添加'example.com'到列表.

#三、start_urls = ['http://www.amazon.cn/']
若是沒有指定start_requests,就從該列表中讀取url來生成第一個請求

#四、custom_settings
值爲一個字典,定義一些配置信息,在運行爬蟲程序時,這些配置會覆蓋項目級別的配置
因此custom_settings必須被定義成一個類屬性
 
#五、settings
經過self.settings['配置項的名字']能夠訪問settings.py中的配置,若是本身定義了custom_settings仍是以本身的爲準

#六、logger
日誌名默認爲spider的名字
self.logger.debug('=============>%s' %self.settings['BOT_NAME'])


#七、start_requests()
該方法用來發起第一個Requests請求,且必須返回一個可迭代的對象。它在爬蟲程序打開時就被Scrapy調用,Scrapy只調用它一次。
默認從start_urls裏取出每一個url來生成Request(url, dont_filter=True)
        
#八、parse(response)
這是默認的回調函數,全部的回調函數必須返回an iterable of Request and/or dicts or Item objects.

#九、closed(reason)
爬蟲程序結束時自動觸發

自定義去重規則

在爬取網頁的過程當中可能會爬到一些重複的網頁,這就須要制定去重規則了,默認狀況下scrapy就會自動幫咱們去除

去重規則應該多個爬蟲共享的,但凡一個爬蟲爬取了,其餘都不要爬了,實現方式以下

#方法一:
一、新增類屬性
visited=set() #類屬性

二、回調函數parse方法內:
def parse(self, response):
    if response.url in self.visited:
        return None
    .......

    self.visited.add(response.url) 

#方法一改進:針對url可能過長,因此咱們存放url的hash值
def parse(self, response):
        url=md5(response.request.url)
    if url in self.visited:
        return None
    .......

    self.visited.add(url) 

#方法二:Scrapy自帶去重功能
配置文件:
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #默認的去重規則幫咱們去重,去重規則在內存中
DUPEFILTER_DEBUG = False # 是否記錄全部重複請求 默認爲第一個重複請求
JOBDIR = "保存範文記錄的日誌路徑,如:/root/"  # 最終路徑爲 /root/requests.seen,去重規則放文件中

scrapy自帶去重規則默認爲RFPDupeFilter,只須要咱們指定
Request(...,dont_filter=False) ,若是dont_filter=True則告訴Scrapy這個URL不參與去重。

#方法三:
咱們也能夠仿照RFPDupeFilter自定義去重規則,

from scrapy.dupefilter import RFPDupeFilter,看源碼,仿照BaseDupeFilter

#步驟一:在項目目錄下自定義去重文件dup.py
class UrlFilter(object):
    def __init__(self):
        self.visited = set() #或者放到數據庫

    @classmethod
    def from_settings(cls, settings):
        return cls()

    def request_seen(self, request):
        if request.url in self.visited:
            return True
        self.visited.add(request.url)

    def open(self):  # can return deferred
        pass

    def close(self, reason):  # can return a deferred
        pass

    def log(self, request, spider):  # log that a request has been filtered
        pass

#步驟二:配置文件settings.py:
DUPEFILTER_CLASS = '項目名.dup.UrlFilter'


# 源碼分析:
from scrapy.core.scheduler import Scheduler
見Scheduler下的enqueue_request方法:self.df.request_seen(request)

數據解析

response經常使用屬性與方法
text 獲取文本
body 獲取二進制
css() css選擇器
xpath() xptah解析

css與xpath返回值都是selector類型

selector經常使用方法
extract   提取字符串形式數據
extract_first 提取第一個
css() 在當前文檔上繼續查找其餘元素  返回selector類型
xpath()在當前文檔上繼續查找其餘元素 返回selector類型

數據持久化

scrapy中使用item來做爲數據模型,pipeline做爲數據持久化組件

items.py

找到items文件 爲itme類添加所需的屬性

class PicItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    url = scrapy.Field()
pipelines.py

一個pipeline應該具有如下幾個方法

class MyPipeline():
    def process_item(self, item, spider):
        """
        負責將一個Item進行持久化
        返回值將繼續傳遞給下一個pipeline(若是有),即使是None
        """
        return item
    
    def open_spider(self, spider):
        """
        爬蟲程序啓動時執行該函數
        用於初始化操做例如鏈接數據庫
        """
        pass

    def close_spider(self, spider):
        """
        爬蟲程序關閉時執行該函數
        用於初清理操做例如關閉數據庫
        """
        pass
   
    # 建立pipeline 會自動查看是否存在該方法若是存在則直接調用 用於獲取一個pipline
    # crawler中的settings屬性能夠獲取配置文件信息
    @classmethod
    def from_crawler(cls, crawler):
        # 從配置文件中讀取數據來建立pipline
        def get(key):
            return crawler.settings.get(key)
        return cls(get("HOST"),get("USER"),get("PWD"),get("DB"))
代碼寫完後須要到配置文件中添加對應的配置項
#數字表示優先級,數值越小優先級越高
ITEM_PIPELINES = {
    "name.pipelines.MysqlPipeline":10,
    "name.pipelines.JsonPipeline":20
}
一個基於文件的實例
class JsonPipeline():
    def __init__(self):
        self.json_data = []
    # 在這裏處理item的持久化
    def process_item(self, item, spider):
        self.json_data.append(dict(item))
        return item

    def open_spider(self, spider):
        pass

    def close_spider(self, spider):
        with open("pics.json","wt") as f:
            json.dump(self.json_data,f)
中斷pipeline的繼續調用

process_item函數一旦有返回值就會繼續執行後續的pipeline,即時返回None,能夠是使用如下方法中斷

#導入 DropItem類
from scrapy.exceptions import  DropItem
# 在process_item中拋出異常
def process_item(self, item, spider):
    raise DropItem

下載器中間件

下載器主要負責從網絡上下載數據,下載器中間件用於對請求與響應進行處理

例如:設置cookie,header,添加代理等等

class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        該方法在下載器發起請求前執行
        :param request: 
        :param spider: 
        :return:  
            None,繼續後續中間件去下載;
            Response對象,中止process_request的執行,開始執行process_response
            Request對象,中止中間件的執行,將Request從新調度器
            raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception
        """
        pass

    def process_response(self, request, response, spider):
        """
        數據下載完成,返回spider前執行
        :param response:
        :param result:
        :param spider:
        :return: 
            Response 對象:轉交給其餘中間件process_response
            Request 對象:中止中間件,request會被從新調度下載
            raise IgnoreRequest 異常:調用Request.errback
        """
        print('response1')
        return response

    def process_exception(self, request, exception, spider):
        """
        當下載處理器(download handler)或 process_request() (下載中間件)拋出異常時執行
        :param response:
        :param exception:
        :param spider:
        :return: 
            None:繼續交給後續中間件處理異常;
            Response對象:中止後續process_exception方法
            Request對象:中止中間件,request將會被從新調用下載
        """
        return None
代碼寫完後須要到配置文件中添加對應的配置項
DOWNLOADER_MIDDLEWARES = {
   'name.middlewares.TDownloaderMiddleware': 100,
}
須要注意的是優先級必定要比系統的高,由於scrapy本身有個proxy中間件

ip代理池

首先須要明確代理池的原理,從網頁上爬取免費的代理地址數據,在請求時,若是對方服務器,限制訪問IP,就從代理池中獲取代理地址從新訪問

網絡上有不少線程的開源代理池,這裏以IPProxyPool 爲例

下載地址:https://github.com/qiyeboy/IPProxyPool

1.下載代理池

2.安裝依賴

​ 在代代理池目錄中找到requirements.txt 複製其路徑 執行如下命令

​ pip3 install -r 文件路徑

3.下載webpy https://codeload.github.com/webpy/webpy/zip/py3

​ 安裝webpy

​ 切換目錄到webpy文件夾中執行如下命令

​ python setup.py install

4.運行IPProxy.py文件開始爬取ip

​ 執行過程可能報錯,根據錯誤信息安裝對應的模塊便可

5.在項目中請求代理池的接口獲取代理ip

import requests,random
ips = None
def get_proxy():
    global ips
    if not ips:
        ips = requests.get("http://127.0.0.1:8001/").json()
    a = random.choice(ips)
    return "http://"+a[0]+":"+str(a[1])

def delete_ip(ip):
    ip = ip.strip("http://").split(":")[0]
    res = requests.get("http://127.0.0.1:8001/delete?ip="+ip).json()
    print(res)

if __name__ == '__main__':
    # print(get_proxy())
    #delete_ip("http://60.184.34.232:9999")
相關文章
相關標籤/搜索