Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 其能夠應用在數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
其最初是爲了頁面抓取 (更確切來講, 網絡抓取 )所設計的, 也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。html
Scrapy主要包括瞭如下組件:python
scrapy startproject your_project_name
項目文件說明:web
在項目目錄下輸入:ajax
scrapy genspider 文件名 域名
更改以下設置:正則表達式
# Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' \ '(KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR'
scrapy默認遵照robotstxt,改成False,並把USER_AGENT改成瀏覽器,增長 LOG_LEVEL = 'ERROR',設置爲出錯時纔打印日誌文件。瀏覽器
進入project_name目錄,運行命令cookie
scrapy crawl spider_name [--nolog]
--nolog 不顯示日誌 網絡
爬取數據並進行持久化處理併發
items.py:框架
# 規範持久化的格式 import scrapy class MyspiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() url=scrapy.Field()
爬蟲應用:
import scrapy from myspider.items import MyspiderItem class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] def parse(self, response): # print(response.text) a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract() # extract()將標籤中數據取出 for url in a_list: yield MyspiderItem(url=url)
yield每執行一次,process_item就調用yield Item對象。
此處代碼的關鍵在於:
使用scrapy解析文本內容時,可使用每一個應用中的response.xpath(xxx) 進行數據的解析。
print(response.xpath(...)) 獲得的是一個Selector對象。selector對象能夠繼續xpath進行數據的解析。
response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8') hxs = HtmlXPathSelector(response) print(hxs) # selector對象 hxs = Selector(response=response).xpath('//a') print(hxs) #查找全部的a標籤 hxs = Selector(response=response).xpath('//a[2]') print(hxs) #查找某一個具體的a標籤 取第三個a標籤 hxs = Selector(response=response).xpath('//a[@id]') print(hxs) #查找全部含有id屬性的a標籤 hxs = Selector(response=response).xpath('//a[@id="i1"]') print(hxs) # 查找含有id=「i1」的a標籤 # hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]') # print(hxs) # 查找含有href=‘xxx’而且id=‘xxx’的a標籤 # hxs = Selector(response=response).xpath('//a[contains(@href, "link")]') # print(hxs) # 查找 href屬性值中包含有‘link’的a標籤 # hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]') # print(hxs) # 查找 href屬性值以‘link’開始的a標籤 # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]') # print(hxs) # 正則匹配的用法 匹配id屬性的值爲數字的a標籤 # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract() # print(hxs) # 匹配id屬性的值爲數字的a標籤的文本內容 # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract() # print(hxs) #匹配id屬性的值爲數字的a標籤的href屬性值 # hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract() # print(hxs) # hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first() # print(hxs) # ul_list = Selector(response=response).xpath('//body/ul/li') # for item in ul_list: # v = item.xpath('./a/span') # # 或 # # v = item.xpath('a/span') # # 或 # # v = item.xpath('*/a/span') # print(v)
備註:xpath中支持正則的使用:
1.標籤+[re:test(@屬性值,"正則表達式")]
2.獲取標籤的文本內容: /text()
pipelines.py:
class MyspiderPipeline(object): def __init__(self,file_path): self.f = None self.file_path = file_path @classmethod def from_crawler(cls,crawler): ''' 執行pipeline類時,會先去類中找from_crawler的方法, 若是有,則先執行此方法,而且返回一個當前類的對象, 若是沒有,則直接執行初始化方法 :param crawler: :return: ''' # 能夠進行一些初始化以前的處理,好比:文件的路徑配置到settings文件中,方便後期的更改。 file_path = crawler.settings.get('PACHONG_FILE_PATH') return cls(file_path) def open_spider(self,spider): ''' 爬蟲開始時被調用 :param spider: :return: ''' self.f = open(self.file_path,'w',encoding='utf8') def process_item(self, item, spider): ''' 執行持久化的邏輯操做 :param item: 爬蟲yield過來的item對象 (一個字典) :param spider: 爬蟲對象 :return: ''' self.f.write(item['url']+'\n') self.f.flush() #將寫入到內存的文件強刷到文件中,防止夯住,不使用此方法會夯住 return item def close_spider(self,spider): ''' 爬蟲結束時調用 :param spider: :return: ''' self.f.close()
上述中的pipelines中能夠有多個類,定義各個類的優先級,要在settings.py中作以下配置:
ITEM_PIPELINES = { 'xxx': 300, 'xxx': 100, } # 每行後面的整型值,肯定了他們運行的順序,item按數字從低到高的順序,經過pipeline,一般將這些數字定義在0-1000範圍內。
獲取全部頁面:
import scrapy from myspider.items import MyspiderItem from scrapy.http import Request class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] def parse(self, response): a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract() for url in a_list: yield MyspiderItem(url=url) # 獲取分頁的url url_list = response.xpath('//div[@id="dig_lcpage"]//a/@href').extract() for url in url_list: url = 'https://dig.chouti.com%s'%url yield Request(url=url,callback=self.parse)
以上代碼之因此能夠進行「遞歸」的訪問相關URL,關鍵在於parse方法使用了 yield Request對象。
注:能夠修改settings.py 中的配置文件,以此來指定「遞歸」的層數,如: DEPTH_LIMIT = 1
class Spider(object_ref): def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True)
備註:在執行爬蟲應用時,會先執行start_requests方法,因此咱們能夠重寫此方法自定製。
使用請求傳參緣由:須要爬取的數據不在同一頁面
class MovieSpider(scrapy.Spider): name = 'movie' allowed_domains = ['www.id97.com'] start_urls = ['http://www.id97.com/'] def parse(self, response): div_list = response.xpath('//div[@class="col-xs-1-5 movie-item"]') for div in div_list: item = MovieproItem() item['name'] = div.xpath('.//h1/a/text()').extract_first() item['score'] = div.xpath('.//h1/em/text()').extract_first() #xpath(string(.))表示提取當前節點下全部子節點中的數據值(.)表示當前節點 item['kind'] = div.xpath('.//div[@class="otherinfo"]').xpath('string(.)').extract_first() item['detail_url'] = div.xpath('./div/a/@href').extract_first() #請求二級詳情頁面,解析二級頁面中的相應內容,經過meta參數進行Request的數據傳遞 yield scrapy.Request(url=item['detail_url'],callback=self.parse_detail,meta={'item':item}) def parse_detail(self,response): #經過response獲取item item = response.meta['item'] item['actor'] = response.xpath('//div[@class="row"]//table/tr[1]/a/text()').extract_first() item['time'] = response.xpath('//div[@class="row"]//table/tr[7]/td[2]/text()').extract_first() item['long'] = response.xpath('//div[@class="row"]//table/tr[8]/td[2]/text()').extract_first() #提交item到管道 yield item
scrapy.Request函數經過meta參數把item傳遞給回調函數parse_detail,parse_detail能夠經過reponse.meta調用item,在該函數內完成對二級頁面的爬取,最後將item提交給管道。
下載器中間件是介於Scrapy的request/response處理的鉤子框架,是用於全局修改Scrapy request和response的一個輕量、底層的系統。
主要做用:
scrapy內置了一些默認配置,這些是不容許被修改的,一般是_BASE結尾的設置,好比DOWNLOADER_MIDDLEWARES_BASE下載中間件的默認設置,以下
{ 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, }
scrapy就是按照上面數字從小到大依次執行的,好比執行完RobotsTxtMiddleware的process_request()方法後會繼續執行下面HttpAuthMiddleware等process_request(),能夠看做串聯的形式依次過流水線。
若是咱們要添加自定義的下載中間件,須要在settings.py中激活DOWNLOADER_MIDDLEWARES。同時想取消默認的一些中間件,也能夠設置爲None。注意的是激活DOWNLOADER_MIDDLEWARES並不會覆蓋DOWNLOADER_MIDDLEWARES_BASE,而是繼續串聯起來
DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMiddleware': 543, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, }
在建立項目後,再項目文件夾中有一middlewares.py文件,裏面自動生成了兩個中間件示例或者說模板。咱們若是要自定義中間件的話,能夠在給的示例上修改,或者新建類實現方法,或者繼承已有的中間件重寫方法
如下是下載中間件能夠實現的方法,在自定義中間件時,能夠根據需求實現
1.process_request(self, request, spider)
參數:
crawler(Crawlerobject)- 使用此中間件的爬網程序
設置隨機User-Agent的中間件:
user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5" ] class UserAgent_Middleware(): def process_request(self, request, spider): ua = random.choice(user_agent_list) request.headers['User-Agent'] = ua
proxy_list=[ "http://180.76.154.5:8888", "http://14.109.107.1:8998", "http://106.46.136.159:808", "http://175.155.24.107:808", "http://124.88.67.10:80", "http://124.88.67.14:80", "http://58.23.122.79:8118", "http://123.157.146.116:8123", "http://124.88.67.21:843", "http://106.46.136.226:808", "http://101.81.120.58:8118", "http://180.175.145.148:808"] class proxy_Middleware(object): def process_request(self,request,spider): proxy = random.choice(proxy_list) request.meta['proxy'] = proxy
集成selenium的scrapy(selenium用於爬取動態加載的數據)
from selenium import webdriver from scrapy.http import HtmlResponse import time class SeleniumMiddleware(object): def __init__(self): self.driver = webdriver.Chrome() def process_request(self, request, spider): self.driver.get(request.url) time.sleep(2) body = self.driver.page_source return HtmlResponse(self.driver.current_url, body=body, encoding='utf-8', request=request)
最好是在爬蟲文件中建立瀏覽器對象,而後在中間件middlewares.py中經過spider.xxx調用瀏覽器對象,避免屢次建立。
spider中間件用於處理引擎傳回的response及spider生成的item和Request
主要做用:
SPIDER_MIDDLEWARES_BASE
{ 'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500, 'scrapy.spidermiddlewares.referer.RefererMiddleware': 700, 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800, 'scrapy.spidermiddlewares.depth.DepthMiddleware': 900, }
同理,激活中間件
SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None, }
1.process_spider_input(self, response, spider)
對於經過spider中間件並進入spider的每一個響應,都會調用此方法進行處理。
process_spider_input()應該返回None或提出異常。
參數:
response(Responseobject) - 正在處理的響應
spider(Spiderobject) - 此響應所針對的spider
2.process_spider_output(self, response, result, spider)
在處理完響應以後,使用Spider返回的結果調用此方法。
process_spider_output()必須返回一個可迭代的 Request,dict或Item 對象。
參數:
response(Responseobject) - 從spider生成此輸出的響應
result(可迭代的Request,dict或Item對象) - spider返回的結果
spider(Spiderobject) - 正在處理其結果的spider
3.process_spider_exception(self, response, exception, spider)
當spider或process_spider_output() 方法(來自先前的spider中間件)引起異常時,將調用此方法。
process_spider_exception()應該返回一個None或一個可迭代的Request,dict或 Item對象。
參數:
response(Responseobject) - 引起異常時正在處理的響應
exception(異常對象) - 引起異常
spider(Spiderobject) - 引起異常的spider
4.process_start_requests(self, start_requests, spider)
當spider運行到start_requests()的時候,爬蟲中間件的process_start_requests()方法被調用
它接收一個iterable(在start_requests參數中)而且必須返回另外一個可迭代的Request對象。
參數:
start_requests(可迭代Request) - 開始請求
spider(Spiderobject) - 啓動請求所屬的spider
5.from_crawler(cls, crawler)
這個類方法一般是訪問settings和signals的入口函數
1.spider開始start_requests()的時候,spider中間件的process_start_requests()方法被調用
2.下載response成功後,返回到spider 回調函數parse前,調用process_spider_input()
3.當spider yield scrapy.Request()或者yield item的時候,spider中間件的process_spider_output()方法被調用。
4.當spider出現了Exception的時候,spider中間件的process_spider_exception()方法被調用。
做用:全站數據爬取,處理不在同一頁面的數據
建立命令:
scrapy startproject xx
cd xx
scrapy genspider -t crawl xxx xxxx.com
屬性:
class ChoutiSpider(CrawlSpider): name = 'chouti' # allowed_domains = ['dig.chouti.com'] start_urls = ['https://dig.chouti.com'] # 連接提取器:從起始url對應的頁面中提取符合規則的連接,參數allow=正則表達式,提取符合該正則的連接 link = LinkExtractor(allow=r'Items/') rules = ( # 規則解析器:將連接提取器提取到的連接對應的頁面源碼進行指定規則(callback)的解析 # follow=True 將連接提取器繼續做用到連接提取器提取到的連接對應的頁面源碼中(便可以提取到全部分頁) Rule(link, callback='parse_item', follow=True), )
- scrapy使用流程 - 建立一個工程:scrapy startproject xxx - cd xxx - 建立爬蟲文件:scrapy genspider xxx www.xxx.com - 執行:scrapy crawl xxx- 持久化存儲: - 基於終端指令:scrapy crawl xxx -o file_path.csv - 好處:便捷 - 弊端:侷限性強(只能夠寫入本地文件,文件類型有限制) - 基於管道: - 數據解析 - 在item類中聲明相關的屬性用於存儲解析到的數據 - 將解析到的數據存儲封裝到item類型的對象中 - 將item對象提交給管道 - item會被管道類中的process_item方法中的item參數進行接收 - 在process_item方法中編寫基於item持久化存儲的操做 - 在配置文件中開啓管道 - 管道細節處理 - 管道文件中的一個類表示的是將解析到的數據存儲到某一個具體的平臺中 - process_item的返回值(return item)就是將item傳遞給下一個即將被執行的管道類 - open_spider, close_spider的使用- 請求傳參 - 應用場景: 爬取得數據不在同一頁面 - 實現:scrapy.Request(url,callback,meta={'':''}) callback回調函數內經過response.meta['']調用傳遞的參數 - 中間件 - 批量攔截請求和響應 - 攔截請求:UA假裝(process_request), 代理IP(process_exception: return request) - 攔截響應:process_response - scrapy+selenium- crawlspider 做用:全站數據爬取 - spider的一個子類 - LinkExtractor:連接提取器:從起始url對應的頁面中提取符合規則的連接,參數allow=正則表達式,提取符合該正則的連接 - Rule:規則解析器:將連接提取器提取到的連接對應的頁面源碼進行指定規則(callback)的解析 - 一個連接提取器對用一個規則解析器