Scrapy

1.介紹

Scrapy,Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的數據。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。Scrapy使用了Twisted異步網絡庫來處理網絡通信,總體架構大體以下:css

Scrapy主要包括瞭如下組件:html

  • 引擎(Scrapy Engine)

Scrapy Engine是整個框架的核心,用來處理整個系統的數據流處理。python

  • 調度器(Scheduler)

用來接受引擎發過來的請求,也就是調度抓取網頁連接的優先隊列,由它來決定下一個要抓取的網址是什麼,同時去除重複的網址。web

  • 下載器(Downloader)

用於下載網頁內容,創建在twisted高效異步模型上,整個Scrapy框架都創建在這個模型上。正則表達式

  • 爬蟲(Spider)

用戶定製的爬蟲組件,用於從特定的網頁信息上提取本身須要的信息,也能夠從中提取連接,讓Scrapy繼續抓取下一次要抓取的頁面。數據庫

  • 項目管道(Item Pipeline)

負責處理用戶定製爬蟲中提取的Item,主要的功能是持久化項目,驗證項目的有效性,清除不須要的信息。當頁面被爬蟲解析後,將被髮送給項目管道,並通過幾個特定的次序處理數據。緩存

  • 下載器中間件(Downloader Middlewares)

位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。cookie

  • 爬蟲中間件(Spider Middlewares)

介於Scrapy引擎和爬蟲之間的框架,主要工做是處理spider的響應輸入和請求輸出。網絡

  • 調度器中間件(Scheduler Middlewares)

介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。session

Scrapy運行流程大概以下:

  1. 引擎從調度器中取出一個連接(URL)用於接下來的抓取
  2. 引擎把URL封裝成一個請求(Request)傳給下載器
  3. 下載器把資源下載下來,並封裝成應答包(Response)
  4. 爬蟲解析Response
  5. 解析出實體(Item),則交給實體管道進行進一步的處理
  6. 解析出的是連接(URL),則把URL交給調度器等待抓取

2.安裝

1 pip3 install wheel
2 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/   #twisted
3 進入下載目錄,執行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
4 pip3 install scrapy
5 下載並安裝pywin32:https://sourceforge.net/projects/pywin32/files/

3.創建項目

1 scrapy startproject 項目名稱
2 cd 項目名稱目錄
3 scrapy genspider 爬蟲名 網址域名

4.運行

1 scrapy crawl banmakj --nolog     # 單獨運行   --nolog不顯示日誌
2 scrapy crawl banmakj -s JOBDIR=job1   # 中斷運行,可繼續從中斷的地方繼續爬取
3 # 在項目文件下,新建run.py,運行run.py,便於程序的調試
4 from scrapy import cmdline
5 cmdline.execute('scrapy crawl banmakj'.split())

自定製命令

  • 在spiders同級建立任意目錄,如commands
  • 進入commands目錄,建立crawl_all.py文件(好比一次運行多個爬蟲文件)
  • 在settings.py中添加配置COMMANDS_MODULE = ' 項目名稱.commands '
  • 執行命令:scrapy crawl_all
from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings
class Command(ScrapyCommand):
    requires_project = True
    def syntax(self):
        return '[options]'
    def short_desc(self):
        return 'Runs all of the spiders'    # help提示信息
    def run(self, args, opts):
        spider_list = self.crawler_process.spiders.list() # 獲取全部的爬蟲名字
        for name in spider_list:
            self.crawler_process.crawl(name, **opts.__dict__)
        self.crawler_process.start()

win下終端運行scrapy沒有內容顯示,多是編碼的問題,在spider.py文件中加

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

5.解析器

Selectors選擇器簡介:從網頁中提取數據有不少方法。Scrapy使用了一種基於 XPath 和 CSS 表達式機制: Scrapy Selectors 。 關於selector和其餘提取機制的信息請參考 Selector文檔

from scrapy.selector import Selector  # 查看Selector源碼

這裏給出Xpath表達式的例子及對應的含義:

  • //a:選擇a標籤下的全部元素
  • ./a:當前標籤下的元素
  • /text():獲取文本
  • /@href:獲取屬性值
  • //a[@id="i1"]:選擇a標籤id='i1'下的全部元素
  • //a[starts-with(@href, "/link/12/")]:屬性開頭
  • //a[re:test(@href,"/link/\d+")]:使用正則表達式

爲了配合XPath,Scrapy除了提供了Selector以外,還提供了方法來避免每次從response中提取數據時生成selector的麻煩。
Selector有四個基本的方法:

  • xpath(): 傳入xpath表達式,返回該表達式所對應的全部節點的selector list列表 。
  • css(): 傳入CSS表達式,返回該表達式所對應的全部節點的selector list列表。
  • extract(): 序列化該節點爲unicode字符串並返回list。
  • re(): 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表。
from scrapy.selector import Selector
...
html = Selector(response=response).xpath('...')

# 簡潔寫法
...
html = response.xpath('...')

使用第三方庫xpath的解析方法:

from lxml import etree
...
selector = etree.HTML(response.text)
content = selector.xpath('...')

# 網頁內容
response.text

6.URL

scrapy默認會訪問start_urls,調用start_requests方法,以及parse方法,進行初始化url。能夠重寫此方法,這樣就能夠訪問咱們任意設置的初始化url和自定義parse方法。

在settings中設置遞歸深度 DEPTH_LIMIT = 4 指定遞歸的層數爲4層。

def start_requests(self):
    url = '...'
    yield request(url,callback=self.custom_parse)

scrapy默認url去重源碼在from scrapy.dupefilters import RFPDupeFilter類中,經過建立一個本地文件以追加的方式打開,把url存儲到文件中,而後利用set集合去重。

dont_filter = False scrapy默認開啓去重,若是想屢次請求同一個url,能夠將其設爲True。

其實,咱們能夠自定義scrapy的去重方法,在spiders同級目錄下,建立duplication.py文件,添加RepeatFilter類。

在settings中設置 DUPEFILTER_CLASS = '項目名稱.duplication.RepeatFilter'

# 去重函數在scrapy開始運行的時候就已經執行了
class RepeatFilter(object):
    def __init__(self):           # 第2步 初始化
        self.visited_url=set()    # 能夠在這裏設置寫到數據庫、緩存中... scrapy默認寫到文件中

    @classmethod   # obj = RepeatFilter.from_setting()  返回cls()等於調用了RepeatFilter()
    def from_settings(cls, settings):
        print('from_setting')        # 第1步 建立對象
        return cls()

    def request_seen(self, request):    #   第4步 set集合去重 
        if request.url in self.visited_url:
            return True
        self.visited_url.add(request.url)
        return False

    def open(self):   # 第3步 打開spider
        print('open')
        pass

    def close(self, reason):  #  第5步 關閉
        print('close')
        pass

    def log(self, request, spider):
        pass

scrapy用set()去重帶來的弊端就是url長度不一致,佔據的內存空間大,讀取訪問速度也相對較慢。能夠經過對url加密的方式,如md5,長度相同,同一url的mad5值相同,存儲長度也同樣。

import hashlib
def md5(url):
    obj = hashlib.md5()
    obj.update(bytes(url,encoding='utf-8'))
    return obj.hexdigest()

7.Items

  •  編寫目標字段
  • 在spiders.py文件中導入item類
  • yield item,傳遞給Pipeline

8.Pipeline

  •  在settings中開啓ITEM_PIPELINES
  • 自定義Pipeline類,在類中process_item每次必須yield item,否則不會給下一個Pipeline文件,若是想丟棄一個item不被下一個Pipeline調用,必須拋出一個異常,而不是不返回item,用這種方法能夠知道在哪裏拋棄了item。
from scrpay.exceptions import DropItem

class SaveMongDBPipline(object):
    def __init__(self,mong_url):
        self.mong_url = mong_url
    @classmethod       # 初始化時候,用於建立pipeline對象,從setting中導入配置文件
    def from_crawler(cls,crawler):     # crawler中封裝了settings 配置文件必須大寫。
        mong_url = crawler.settings.get("MONG_URL")
        return cls(mong_url)
    def process_item(self,item,spider):
        # 保存item到mongdb 
   # raise DropItem      不返回item 拋出異常
        yield item
    def open_spider(self,spider):
        # 打開數據庫 爬蟲開始執行時被調用
    def close_spider(self,spider):
        # 關閉數據庫 爬蟲結束時被調用

9.Cookies

 第一種方法:

#下面start_requests中鍵‘cookiejar’是一個特殊的鍵,scrapy在meta中見到此鍵後,會自動將cookie傳遞到要callback的函數中。既然是鍵(key),就須要有值(value)與之對應,例子中給了數字1,也能夠是其餘值,好比任意一個字符串。
def start_requests(self):
    yield Request(url,meta={'cookiejar':1},callback=self.parse)
#須要說明的是,meta給‘cookiejar’賦值除了能夠代表要把cookie傳遞下去,還能夠對cookie作標記。一個cookie表示一個會話(session),若是須要經多個會話對某網站進行爬取,能夠對cookie作標記,1,2,3,4......這樣scrapy就維持了多個會話。
def parse(self,response):
    key=response.meta['cookiejar']    #通過此操做後,key=1
    yield Request(url2,meta={'cookiejar':key},callback='parse2')
def parse2(self,response):
    pass
#上面這段和下面這段是等效的:
def parse(self,response):
    yield Request(url2,meta={'cookiejar':response.meta['cookiejar']},callback='parse2')
    #這樣cookiejar的標記符仍是數字1
def parse2(self,response):
    pass

第二種方法:

# 先引入CookieJar()方法
from scrapy.http.cookies import CookieJar
# 寫spider方法時:
def start_requests(self):
    yield Request(url,callback=self.parse)   #此處寫self.parse或‘parse’均可以
def parse(self,response):
    cj = response.meta.setdefault('cookie_jar', CookieJar())
    cj.extract_cookies(response, response.request)
    container = cj._cookies
    yield Request(url2,cookies=container,meta={'key':container},callback='parse2')
def parse2(self,response):
    pass

第三種方法:

cookies = None  # 全局變量保存cookies
def parse(self, response):
    cookie_jar = CookieJar()
    cookie_jar.extract_cookies(response,response.request)
    cookies = cookie_jar._cookies

    yield scrapy.Request(
        ...
        cookies=cookies,
    )
#下一次請求帶上self.cookies

10.擴展(Extensions)

擴展框架提供一個機制,使得你能將自定義功能綁定到Scrapy。擴展只是正常的類,它們在Scrapy啓動時被實例化、初始化。

源碼:from scrapy.extensions.telnet import TelnetConsole

#自定義擴展,新建extensions.py
from scrapy import signals
class MyExtend:
    def __init__(self, crawler):
    # 註冊信號操做,信號觸發的時候,能夠執行不少東西
        self.crawler = crawler
        self.crawler.signals.connect(self.start, signals.engine_started)   # 註冊自定義信號

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler)

    def start(self):
        print('signal.engine_started')

# 擴展所能用的信號
    engine_started = object()
    engine_stopped = object()
    spider_opened = object()
    spider_idle = object()
    spider_closed = object()
    spider_error = object()
    request_scheduled = object()
    request_dropped = object()
    response_received = object()
    response_downloaded = object()
    item_scraped = object()
    item_dropped = object()

11.配置文件(settings)

BOT_NAME = ' '           # 爬蟲的名字 經過user_agent發送出去
ROBOTSTXT_OBEY = True        # 爬蟲規則 False不遵照
CONCURRENT_REQUESTS = 32     # 併發請求數
DOWNLOAD_DELAY = 3           # 下載延時
CONCURRENT_REQUESTS_PER_DOMAIN = 16   # 針對域名併發數
CONCURRENT_REQUESTS_PER_IP = 16       # 針對IP併發
COOKIES_ENABLED = False               # 默認爲True 請求是否攜帶cookies
TELNETCONSOLE_ENABLED = False         # telnet 127.0.0.1 6023 監聽當前爬蟲的狀態 True
DEPTH_PRIORITY = 0 / 1                # 深度優先/廣度優先

12.代理

源碼:from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware

scrapy默認代理依賴環境

os.environ{
    http_proxy:http://root:woshiniba@192.168.11.11:9999/
    https_proxy:http://192.168.11.11:9999/
    }

自定義代理

class ProxyMiddleware(object):
    def process_request(self, request, spider):
        PROXIES = [
            {'ip_port': '111.11.228.75:80', 'user_pass': ''},
            {'ip_port': '120.198.243.22:80', 'user_pass': ''},
        ]
        proxy = random.choice(PROXIES)
        if proxy['user_pass'] is not None:
            request.meta['proxy'] = "http://%s" % proxy['ip_port']
            encoded_user_pass = base64.encodestring(proxy['user_pass'])
            request.headers['Proxy-Authorization'] = b'Basic ' + encoded_user_pass
            print
            "**************ProxyMiddleware have pass************" + proxy['ip_port']
        else:
            print
            "**************ProxyMiddleware no pass************" + proxy['ip_port']
            request.meta['proxy'] = "http://%s" % proxy['ip_port']

DOWNLOADER_MIDDLEWARES = {
        'step8_king.middlewares.ProxyMiddleware': 500,
    }

13.User_Agent

from fake_useragent import UserAgent   # 用戶代理模塊

# 設置隨機的用戶代理
class RandomUserAgentMiddleware(object):
    def __init__(self,crawler):
        super(RandomUserAgentMiddleware,self).__init__()
        self.ua = UserAgent()

    @classmethod
    def from_crawler(cls,crawler):
        return cls(crawler)

    def process_request(self,request,spider):
        request.headers.setdefault('User-Agent',self.ua.random)

14.下載中間件

至關於把request交給了下載中間,返回response通過spider中間件給spider,scrapy中包含不少個下載中間件,能夠在最後調用scrapy默認的中間件以前,能夠自定義

頁面下載器。全部的process_request下載完成後,後續中間件無需下載,process_response返回response給spider。

class DownMiddleware1(object):
    def process_request(self, request, spider):
        '''
        請求須要被下載時,通過全部下載器中間件的process_request調用
            None,繼續後續中間件去下載;
            Response對象,中止process_request的執行,開始執行process_response
            Request對象,中止中間件的執行,將Request從新調度器
            raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception
        '''
        pass

    def process_response(self, request, response, spider):
        '''
        process_request處理完成,返回時調用
            Response 對象:轉交給其餘中間件process_response
            Request 對象:中止中間件,request會被從新調度下載
            raise IgnoreRequest 異常:調用Request.errback
        '''
        print('response1')
        return response

    def process_exception(self, request, exception, spider):
        '''
        當下載處理器(download handler)或 process_request() (下載中間件)拋出異常
            None:繼續交給後續中間件處理異常;
            Response對象:中止後續process_exception方法
            Request對象:中止中間件,request將會被從新調用下載
        '''
        return None


# 執行順序
DownMiddleware1 process_request
DownMiddleware2 process_request
DownMiddleware2 process_response
DownMiddleware1 process_response
spider.response

15.爬蟲中間件

class SpiderMiddleware(object):
    def process_spider_input(self, response, spider):
        '''
        下載完成,執行,而後交給parse處理
        '''
        pass

    def process_spider_output(self, response, result, spider):
        '''
        spider處理完成,返回時調用
        :return: 必須返回包含 Request 或 Item 對象的可迭代對象(iterable)
        '''
        return result

    def process_spider_exception(self, response, exception, spider):
        '''
        異常調用
        :return: None,繼續交給後續中間件處理異常;含 Response 或 Item 的可迭代對象(iterable),交給調度器或pipeline
        '''
        return None

    def process_start_requests(self, start_requests, spider):
        '''
        爬蟲啓動時調用
        :return: 包含 Request 對象的可迭代對象
        '''
        return start_requests
相關文章
相關標籤/搜索