Scrapy,Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的數據。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。Scrapy使用了Twisted異步網絡庫來處理網絡通信,總體架構大體以下:css
Scrapy主要包括瞭如下組件:html
Scrapy Engine是整個框架的核心,用來處理整個系統的數據流處理。python
用來接受引擎發過來的請求,也就是調度抓取網頁連接的優先隊列,由它來決定下一個要抓取的網址是什麼,同時去除重複的網址。web
用於下載網頁內容,創建在twisted高效異步模型上,整個Scrapy框架都創建在這個模型上。正則表達式
用戶定製的爬蟲組件,用於從特定的網頁信息上提取本身須要的信息,也能夠從中提取連接,讓Scrapy繼續抓取下一次要抓取的頁面。數據庫
負責處理用戶定製爬蟲中提取的Item,主要的功能是持久化項目,驗證項目的有效性,清除不須要的信息。當頁面被爬蟲解析後,將被髮送給項目管道,並通過幾個特定的次序處理數據。緩存
位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。cookie
介於Scrapy引擎和爬蟲之間的框架,主要工做是處理spider的響應輸入和請求輸出。網絡
介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。session
Scrapy運行流程大概以下:
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/
1 scrapy startproject 項目名稱 2 cd 項目名稱目錄 3 scrapy genspider 爬蟲名 網址域名
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())
自定製命令
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')
Selectors選擇器簡介:從網頁中提取數據有不少方法。Scrapy使用了一種基於 XPath 和 CSS 表達式機制: Scrapy Selectors 。 關於selector和其餘提取機制的信息請參考 Selector文檔 。
from scrapy.selector import Selector # 查看Selector源碼
這裏給出Xpath表達式的例子及對應的含義:
爲了配合XPath,Scrapy除了提供了Selector以外,還提供了方法來避免每次從response中提取數據時生成selector的麻煩。
Selector有四個基本的方法:
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
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()
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): # 關閉數據庫 爬蟲結束時被調用
第一種方法:
#下面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
擴展框架提供一個機制,使得你能將自定義功能綁定到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()
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 # 深度優先/廣度優先
源碼: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, }
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)
至關於把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
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