scrapy學(xué)習(xí)筆記(有示例版)
- scrapy學(xué)習(xí)筆記
- scrapy學(xué)習(xí)筆記
1.使用scrapy
ps:linux環(huán)境
安裝:sudo install Scrapy
1.1創(chuàng)建工程
$ scrapy startproject Spider
就創(chuàng)建屬于自己的項(xiàng)目
同時(shí)生成一個(gè)Spider目錄:
Spider
|- scrapy.cfg 項(xiàng)目部署文件
|- Spider 該項(xiàng)目的python模塊,可以在這里加入代碼
|- __init__.py
|- items.py 主要是將爬取的非結(jié)構(gòu)性的數(shù)據(jù)源提取結(jié)構(gòu)性數(shù)據(jù)
|- middlewares.py
|- pipelines.py 將爬取的數(shù)據(jù)進(jìn)行持久化存儲(chǔ)
|- __pycache__
|- settings.py 配置文件
|- spiders 放置spider代碼的目錄
|- __init__.py
|- __pycache__
1.2創(chuàng)建爬蟲模塊
import scrapy
class Spider(scrapy.Spider): #創(chuàng)建的Spider是繼承scrapy.Spider類
name = "Spider" #爬蟲的名稱(必須是項(xiàng)目里面唯一的)
allowed_domain = ["xxx.com"] #允許的域名(就是這個(gè)爬蟲爬取鏈接的范圍)
start_urls = ["http://xxx.com/xxx/xxx"] #開始爬取的鏈接
def parse(self, response):
pass
在spiders文件夾創(chuàng)建出這份代碼,除了以上解釋外,里面的parse()是一個(gè)Spider方法,被調(diào)用時(shí),每個(gè)初始url響應(yīng)后返回的Response對(duì)象,將會(huì)作為唯一的參數(shù)傳遞給該方法。該方法負(fù)責(zé)解析返回的數(shù)據(jù),提取數(shù)據(jù)以及生成需要進(jìn)一步處理的url的Responst對(duì)象
1.3網(wǎng)頁解析
創(chuàng)建完爬蟲模塊后就可以進(jìn)行網(wǎng)頁解析了,Scrapy有自己的一套數(shù)據(jù)提取機(jī)制,成為選擇器(selector),它們通過特定的XPath或者CSS表達(dá)式來選擇HTML文件中的某個(gè)部分,Scrapy選擇器構(gòu)建與lxml庫上,這意味著它們?cè)谒俣群徒馕鰷?zhǔn)確性上非常相似。當(dāng)然,也可以使用自己的,比如BeautifulSoup進(jìn)行解析
Selector對(duì)象有四個(gè)基本的方法
- xpath(query):傳入XPath表達(dá)式query,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的selector list列表
- css(query):傳入CSS表達(dá)式query,返回該表達(dá)式所對(duì)應(yīng)的所有節(jié)點(diǎn)的selector list列表
- extract(): 序列化該節(jié)點(diǎn)為Unicode字符串并返回list列表。
- re(regex):根據(jù)傳入的正則表達(dá)式對(duì)數(shù)據(jù)進(jìn)行提取,返回Unicode字符串列表。regex可以是一個(gè)已編譯的正則,也可以是一個(gè)將為re.compile(regex)編譯為正則表達(dá)式的字符串。
在spider類的parse()方法中,其中一個(gè)參數(shù)是response。所以使用選擇器有兩種方法
- 將response傳入Selector:Selector(response).xpath()
Selector(response).xpath('//span/text()').extract() - 或者直接調(diào)用:response.xpath()
response.xpath('//span/text()').extract()
Scrapy提供了一個(gè)簡便的方式來查看表達(dá)式是否正確
打開命令行窗口后輸入:
$ scrapy shell "http://xxx.com"
或者響應(yīng)后
>>>response.xpath('//span/text()').extract()
就可以抽取出響應(yīng)的數(shù)據(jù)了,返回的是Unicode格式
示例代碼中extract()是提取里面的文本,并返回一個(gè) SelectorList實(shí)例
如果只想提取第一個(gè)匹配的元素,可以調(diào)用選擇器 .extract_first(),如果獲取不到則返回None
可以通過使用extract_first(default='xxx')來自定義返回?cái)?shù)值
了解了這些后可以寫出一個(gè)parse()的代碼
假設(shè)這個(gè)網(wǎng)頁我們要提取的數(shù)據(jù)有標(biāo)題,時(shí)間,評(píng)論,鏈接,且他們?cè)诰W(wǎng)頁的xxx里面
def parse(self, response):
papers = response.xpath(".//*[@class='xxx']")
for paper in papers:
url = paper.xpath(".//*[@class='title']/a/@href").extract()[0]
title = paper.xpath(".//*[@class='title']/a/text()").extract()[0]
time = paper.xpath(".//*[@class='ttime']/a/text())".extract()[0]
content = paper.xpath(".//*[@class='content']/a/text()").extract()[0]
item = SpiderItem(url=url, title=title, time=time,title=title, content=content)
yield item
yield scrapy.Request(url=url, callnack=self.parse)
yield item是把所有獲得的數(shù)據(jù)封裝起來,存放在Spideritem
yield scrapy.Request(url=url, callnack=self.parse)則是把獲取到的url利用request對(duì)象構(gòu)造為請(qǐng)求再利用callback回調(diào)到自己指定的Spideritem
1.4 item
Scrapy提供Item類。 Item對(duì)象是用于收集所抓取的數(shù)據(jù)的簡單容器。它們提供了一個(gè)類似字典的 API,具有用于聲明其可用字段的方便的語法
在items.py文件輸入
class SpiderItem(scrapt.Item):
url = scrapy.Field()
time = scrapy.Field()
title = scrapy.Field()
content = scrapy.Field()
就創(chuàng)建好了,如果要拓展item就在原始item上來拓展item
class newSpiderItem(SpiderItem):
xxx = scrapy.Field()
1.5Item Pipeline
當(dāng)item在Spider中被手機(jī)后,它將會(huì)傳遞到Item PipeLine,利用Item PipeLine來進(jìn)行數(shù)據(jù)保存
先上一個(gè)示例代碼
import json
from scrapy.exceptions import DropItem
class SpiderPipeline(object):
def __init__(self):
self.file = open('xxx.json','w')
def process_item(self,item,spider):
if item['title']:
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item
else:
raise DropItem("沒有找到標(biāo)題"+title)
之后在setting.py的ITEM_PIPELINES變了中進(jìn)行激活
ITEM_PIPELINES = {
'Spider.pipelines.spiderPipeline':300}
process_item()方法可以用來判斷是來自于哪個(gè)爬蟲,item是被爬取對(duì)象,spider是爬取該item的Spider
Dropitem是一個(gè)錯(cuò)誤捕獲
300是判斷運(yùn)行順序,范圍為0~1000
除此之外,Scrapy內(nèi)置了一些簡單的存儲(chǔ)方式生成一個(gè)帶有爬取數(shù)據(jù)的輸出文件自帶的類型有:
- JSON
- JSON lines
- CSV
- XMl
- Pickle
- Marshal
食用方法:將命令行切換到項(xiàng)目目錄,比如想保存為csv格式,輸入命令:
$ scrapy crawl Spider -o papers.csv
就可以讓Spider采集到的數(shù)據(jù)保存為papers.csv文件了
1.6運(yùn)行
$ scrapy crawl Spider
就可以運(yùn)行爬蟲了,其中Spider就是我們定義的name
更過命令這里
也可以在spider.py文件添加一下代碼運(yùn)行
if __name =='__main__':
process = CrawlerProcess(get_project_settings())
process.crawl('spider')
process.start()
2.深入Scrapy
通過以上運(yùn)行一個(gè)爬蟲肯定會(huì)覺得還不如自己寫代碼來爬蟲,但Scrapy框架還有很多厲害的地方
2.1Spider
2.1.1Spider
繼承于Spider是最簡單的spider,常用屬性為:
- name
定義此爬蟲名稱的字符串。爬蟲名稱是爬蟲如何由Scrapy定位(和實(shí)例化),因此它必須是唯一的。但是,沒有什么能阻止你實(shí)例化同一個(gè)爬蟲的多個(gè)實(shí)例。這是最重要的爬蟲屬性,它是必需的。 - allowed_domains
允許此爬蟲抓取的域的字符串的可選列表,指定一個(gè)列表可以抓取。 - start_urls
當(dāng)沒有指定特定網(wǎng)址時(shí),爬蟲將開始抓取的網(wǎng)址列表。 - custom_settings
運(yùn)行此爬蟲時(shí)將從項(xiàng)目寬配置覆蓋的設(shè)置字典。詳情在這里 - crawler
此屬性from_crawler()在初始化類后由類方法設(shè)置,并鏈接Crawler到此爬蟲實(shí)例綁定到的對(duì)象。
Crawlers在項(xiàng)目中封裝了很多組件,用于單個(gè)條目訪問(例如擴(kuò)展,中間件,信號(hào)管理器等) - settings
運(yùn)行此爬蟲的配置。 - logger
用Spider創(chuàng)建的Python記錄器name。 - start_requests()
此方法必須返回一個(gè)可迭代對(duì)象,該方法只被調(diào)用一次。
主要用于在啟動(dòng)階段需要post登錄某個(gè)網(wǎng)站時(shí)才使用
示例代碼:import scrapy class Spider(scrapy.Spider): #創(chuàng)建的Spider是繼承scrapy.Spider類 name = "Spider" #爬蟲的名稱(必須是項(xiàng)目里面唯一的) allowed_domain = ["xxx.com"] #允許的域名(就是這個(gè)爬蟲爬取鏈接的范圍) def start_requests(self): return [scrapy.FromRequest("httP://www.xxx.com/login", formdata={'user':'xxx','pass':'xxx'}, callback=self.login)] def login(self, response): pass
2.1.2CrawlSpidel
CrawlCpider除了從Spider繼承過來的屬性外,還提供了一個(gè)新的屬性rules,rules是一個(gè)包含一個(gè)或多個(gè)Rule對(duì)象的集合,每個(gè)Rule對(duì)爬取網(wǎng)站的動(dòng)作定義了特定的規(guī)則。如果多個(gè)Rule匹配了相同的鏈接,則根據(jù)它們?cè)趓ules屬性中被定義的順序,第一個(gè)會(huì)被使用。
Rule類的原型為:
scrapy.contrib,spiders.Rule(link_extractor,callback=None,cb_kwargs=None,follow=None,process_links=None, process_request=None)
參數(shù)說明
link_extractor 是一個(gè)LinkExtractor對(duì)象,定義了如何從爬取到的頁面提取鏈接。
callback回調(diào)函數(shù)接受一個(gè)response作為一個(gè)參數(shù),應(yīng)避免使用parse作為回調(diào)函數(shù)
cb_kwargs包含傳遞給回調(diào)函數(shù)的參數(shù)的字典
follow是一個(gè)布爾值,指定了根據(jù)規(guī)則從respose提取的鏈接是否需要跟進(jìn)
process_links是一個(gè)可調(diào)用的或一個(gè)字符串(在這種情況下,將使用具有該名稱的爬蟲對(duì)象的方法),將使用指定從每個(gè)響應(yīng)提取的每個(gè)鏈接列表調(diào)用該方法link_extractor。這主要用于過濾目的。
process_request 是一個(gè)可調(diào)用的或一個(gè)字符串(在這種情況下,將使用具有該名稱的爬蟲對(duì)象的方法),它將被此規(guī)則提取的每個(gè)請(qǐng)求調(diào)用,并且必須返回一個(gè)請(qǐng)求或無(過濾出請(qǐng)求)
link_extractor里面也大有乾坤,參數(shù)有:
allow: 提取滿足正則表達(dá)式的鏈接
deny: 排除正則表達(dá)式匹配的鏈接,優(yōu)先級(jí)高于allow
allow_domains: 允許的域名,可以是str,list
deny_domains:排除的域名,同上
restrict_xpaths: 提取滿足xpath選擇條件的鏈接,可以是str,list
restrict_css: 提取滿足css選擇條件的鏈接,可以是str,list
tags: 提取指定標(biāo)記下的鏈接,默認(rèn)從a和area中提取,可以是str或list。
attrs: 提取滿足擁有屬性的鏈接,默認(rèn)為href,類型為list
unique:鏈接是否去重,類型為boolean
process_value: 值處理函數(shù)(這個(gè)函數(shù)是對(duì)已經(jīng)捕獲的函數(shù)進(jìn)行修補(bǔ))
以上詳細(xì)在這里
關(guān)于process_value可以看這兩個(gè)函數(shù)示例(來自于stackoverflow):
import scrapy
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors import LinkExtractor
class dmozItem(scrapy.Item):
basic_url = scrapy.Field()
class dmozSpider(CrawlSpider):
name = "dmoz"
allowed_domains = ["scrapy.org"]
start_urls = [
"http://scrapy.org/",
]
def clean_url(value):
return value.replace('/../', '/')
rules = (
Rule(
LinkExtractor(
allow=('\S+/'),
restrict_xpaths=(['.//ul[@class="navigation"]/a[1]',
'.//ul[@class="navigation"]/a[2]',
'.//ul[@class="navigation"]/a[3]',
'.//ul[@class="navigation"]/a[4]',
'.//ul[@class="navigation"]/a[5]']),
process_value=clean_url
),
callback='first_level'),
)
def first_level(self, response):
taco = dmozItem()
taco['basic_url'] = response.url
return taco
def process_value(value):
unique_id = re.search(r"/item/(\d+)", value).group(1)
if unique_id in already_crawled_site_ids:
return None
return value
rules = [Rule(SgmlLinkExtractor(allow=['/item/\d+']), 'parse_item', process_value=process_value)]
2.1.3 XMLFeedSpider
XMLFeedSpider設(shè)計(jì)用于通過以特定節(jié)點(diǎn)名稱迭代XML訂閱源來解析XML訂閱源。迭代器可以選自:iternodes,xml和html。iternodes為了性能原因,建議使用迭代器,因?yàn)閤ml和迭代器html一次生成整個(gè)DOM為了解析它。但是,html當(dāng)使用壞標(biāo)記解析XML時(shí),使用作為迭代器可能很有用。
要設(shè)置迭代器和標(biāo)記名稱,必須定義以下類屬性:
-
iterator
定義要使用的迭代器的字符串。它可以是:- 'iternodes' - 基于正則表達(dá)式的快速迭代器
- 'html'- 使用的迭代器Selector。請(qǐng)記住,這使用DOM解析,并且必須加載所有DOM在內(nèi)存中,這可能是一個(gè)大飼料的問題
- 'xml'- 使用的迭代器Selector。請(qǐng)記住,這使用DOM解析,并且必須加載所有DOM在內(nèi)存中,這可能是一個(gè)大飼料的問題
它默認(rèn)為:'iternodes'。
itertag
一個(gè)具有要迭代的節(jié)點(diǎn)(或元素)的名稱的字符串。示例:
itertag = 'product'-
namespaces
定義該文檔中將使用此爬蟲處理的命名空間的元組列表。在 與將用于自動(dòng)注冊(cè)使用的命名空間 的方法。(prefix, uri)prefixuriregister_namespace()
示例代碼:class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' adapt_response(response)
一種在爬蟲開始解析響應(yīng)之前,在響應(yīng)從爬蟲中間件到達(dá)時(shí)立即接收的方法。它可以用于在解析之前修改響應(yīng)主體。此方法接收響應(yīng)并返回響應(yīng)(它可以是相同的或另一個(gè))。parse_node(response, selector)
對(duì)于與提供的標(biāo)記名稱(itertag)匹配的節(jié)點(diǎn),將調(diào)用此方法。接收Selector每個(gè)節(jié)點(diǎn)的響應(yīng)和 。覆蓋此方法是必需的。否則,你的爬蟲將不工作。此方法必須返回一個(gè)Item對(duì)象,一個(gè) Request對(duì)象或包含任何對(duì)象的迭代器。process_results(response, results)
對(duì)于由爬蟲返回的每個(gè)結(jié)果(Items or Requests),將調(diào)用此方法,并且它將在將結(jié)果返回到框架核心之前執(zhí)行所需的任何最后處理,例如設(shè)置項(xiàng)目ID。它接收結(jié)果列表和產(chǎn)生那些結(jié)果的響應(yīng)。它必須返回結(jié)果列表
2.2 Item Loader
Item Loader提供了一種便捷的方式填充抓取到的items。雖然items可以使用自帶的類字典形式API填充,但是itemsLoader提供了更便捷的API可以分析原始數(shù)據(jù)并對(duì)item進(jìn)行賦值。也就是Items提供保存捉取數(shù)據(jù)的容器,而Item Loader提供的是填充容器的機(jī)制,且可以方便的被拓展,重寫不同字段的解析規(guī)則,這對(duì)大型的爬蟲項(xiàng)目后期維護(hù)非常有利,拓展新的功能更加方便。
以下是Item和Item Loader的對(duì)比代碼:
class SpiderItem(scrapt.Item):
url = scrapy.Field()
time = scrapy.Field()
title = scrapy.Field()
content = scrapy.Field()
def parse(self, response):
i = ItemLoader(item=Product(), response=response)
l.add_xpath('title', '//div[@class="title1"]')
l.add_xpath('title', '//div[@class="title2"]')
l.add_xpath('time', '//div[@class="time"]')
l.add_xpath('content', '//div[@class="content"]')
return l.load_item()
可以發(fā)現(xiàn)的是ItemsLoader里面的name字段可以從頁面中兩個(gè)不同的XPAth位置提取,之后分配給name字段。下面類似操作后把所有數(shù)據(jù)收集起來,再使用l.load_item將數(shù)據(jù)實(shí)際填充到Item中
2.2.1 輸入與輸出處理器
Item Loder實(shí)現(xiàn)數(shù)據(jù)的收集處理和填充的功能都是通過輸入與輸出處理器來實(shí)現(xiàn)的,他們的功能是:
- Item Loder在每個(gè)字段中都包含了一個(gè)輸入處理器和輸出處理器
- 輸入處理器收到response后時(shí)立刻通過add_xpath(),add_css()或者add_value()等方法提取數(shù)據(jù),經(jīng)過輸入處理器的結(jié)果被手機(jī)起來并且保存在Item Loder內(nèi),
- 收集到所有的數(shù)據(jù)后,調(diào)用Item Loder.load_item()方法來填充并返回item對(duì)象。load_item()方法內(nèi)部先調(diào)用輸出處理器來處理收集到的數(shù)據(jù),最后把結(jié)果存入Item中。
下面是2個(gè)栗子:
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join
class ProductLoader(ItemLoader):
default_output_processor = TakeFirst()
name_in = MapCompose(unicode.title)
name_out = Join()
price_in = MapCompose(unicode.strip)
# ...
可以看到,輸入處理器使用_in后綴聲明,而輸出處理器使用_out后綴聲明。您還可以使用ItemLoader.default_input_processor和 ItemLoader.default_output_processor屬性聲明默認(rèn)輸入/輸出 處理器。
輸入和輸出處理器可以在Item Loader定義中聲明,這種方式聲明輸入處理器是很常見的。但是,還可以在item中聲明:
import scrapy
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags
def filter_price(value):
if value.isdigit():
return value
class Product(scrapy.Item):
name = scrapy.Field(
input_processor=MapCompose(remove_tags),
output_processor=Join(),
)
price = scrapy.Field(
input_processor=MapCompose(remove_tags, filter_price),
output_processor=TakeFirst(),
)
以上說了三種處理器聲明方式:
- ItemLoader類中聲明類似field_in和field_out的屬性
- itemd 的字段中聲明
- ItemLoader默認(rèn)處理器ItemLoader.default_input_processor和 ItemLoader.default_output_processor
這三種方式響應(yīng)優(yōu)先級(jí)是從上到下依次遞減
2.2.2 Item Loader Context
所有輸入和輸出處理器之間共享的任意鍵/值的dict。它可以在聲明,實(shí)例化或使用Item Loader時(shí)傳遞。它們用于修改輸入/輸出處理器的行為。
例如,假設(shè)有一個(gè)parse_length接收文本值并從中提取長度的函數(shù):
def parse_length (text , loader_context ):
unit = loader_context 。get ('unit' , 'm' )
#...長度解析代碼在這里...
return parsed_length
通過接受一個(gè)loader_context參數(shù),該函數(shù)顯式地告訴Item Loader它能夠接收一個(gè)Item Loader上下文,因此Item Loader在調(diào)用它時(shí)傳遞當(dāng)前活動(dòng)的上下文,因此處理器功能(parse_length在這種情況下)可以使用它們。
有幾種方法可以修改Item Loader上下文值:
- 通過修改當(dāng)前活動(dòng)的Item Loader上下文(context屬性):
loader = ItemLoader(product) loader.context['unit'] = 'cm' - On Item Loader實(shí)例化(Item Loader構(gòu)造函數(shù)的關(guān)鍵字參數(shù)存儲(chǔ)在Item Loader上下文中):
loader = ItemLoader(product, unit='cm') - On Item Loader聲明,對(duì)于那些支持使用Item Loader上下文實(shí)例化的輸入/輸出處理器。MapCompose是其中之一:
class ProductLoader(ItemLoader): length_out = MapCompose(parse_length, unit='cm')
2.2.3 重用和拓展 Item Loader
隨著你的項(xiàng)目越來越大,越來越多的爬蟲,維護(hù)成為一個(gè)根本的問題,特別是當(dāng)你必須處理每個(gè)爬蟲的許多不同的解析規(guī)則,有很多異常,但也想重用公共處理器。
項(xiàng)目加載器旨在減輕解析規(guī)則的維護(hù)負(fù)擔(dān),同時(shí)不會(huì)失去靈活性,同時(shí)提供了擴(kuò)展和覆蓋它們的方便的機(jī)制。因此,項(xiàng)目加載器支持傳統(tǒng)的python類繼承,以處理特定爬蟲(或爬蟲組)的差異。
例如,假設(shè)某個(gè)特定站點(diǎn)以三個(gè)短劃線(例如)包含其產(chǎn)品名稱,并且您不希望最終在最終產(chǎn)品名稱中刪除那些破折號(hào)?!狿lasma TV—
以下是如何通過重用和擴(kuò)展默認(rèn)產(chǎn)品項(xiàng)目Loader(ProductLoader)來刪除這些破折號(hào):
from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
def strip_dashes(x):
return x.strip('-')
class SiteSpecificLoader(ProductLoader):
name_in = MapCompose(strip_dashes, ProductLoader.name_in)
另一種擴(kuò)展項(xiàng)目加載器可能非常有用的情況是,當(dāng)您有多種源格式,例如XML和HTML。在XML版本中,您可能想要?jiǎng)h除CDATA事件。下面是一個(gè)如何做的例子:
from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
from myproject.utils.xml import remove_cdata
class XmlProductLoader(ProductLoader):
name_in = MapCompose(remove_cdata, ProductLoader.name_in)
對(duì)于輸出處理器,更常用的方式是在item字段元數(shù)據(jù)里聲明,因?yàn)橥ǔK鼈円蕾囉诰唧w的字段而不是網(wǎng)站
2.2.4 內(nèi)置的處理器
除了可以使用可調(diào)用的函數(shù)作為輸入輸出處理器,Scrapy提供了一些常用的處理器,下面是一些內(nèi)置的處理器
- Identity
類原型: class scrapy.loader.processors.Identity
最簡單的處理器,不進(jìn)行任何處理,直接返回原來的數(shù)據(jù),無參數(shù) - TakeFirst
類原型: class scrapy.loader.processors.TakeFirst
從接收到的值中返回第一個(gè)非空值/非空值,因此它通常用作單值字段的輸出處理器。它不接收任何構(gòu)造函數(shù)參數(shù),也不接受Loader上下文>>> from scrapy.loader.processors import TakeFirst >>> proc = TakeFirst() >>> proc(['', 'one', 'two', 'three']) 'one' - Join
類原型: class scrapy.loader.processors.Join(separator=u'')
返回用分隔符separatoe連續(xù)后的值,分隔符seeparator默認(rèn)為空格。不接受Loader contexts。當(dāng)使用默認(rèn)分隔符的時(shí)候,這個(gè)處理器等同于Python字符串對(duì)象中join方法:''.join>>> from scrapy.loader.processors import Join >>> proc = Join() >>> proc(['one', 'two', 'three']) u'one two three' >>> proc = Join('<br>') >>> proc(['one', 'two', 'three']) u'one<br>two<br>three' - Compose
類原型: class scrapy.loader.processors.Compose(functions,*default_loader_context)
由給定函數(shù)的組合構(gòu)成的處理器。這意味著該處理器的每個(gè)輸入值都被傳遞給第一個(gè)函數(shù),并且該函數(shù)的結(jié)果被傳遞給第二個(gè)函數(shù),依此類推,直到最后一個(gè)函數(shù)返回該處理器的輸出值。
默認(rèn)情況下,停止進(jìn)程N(yùn)one值??梢酝ㄟ^傳遞關(guān)鍵字參數(shù)來更改此行為stop_on_none=False>>> from scrapy.loader.processors import Compose >>> proc = Compose(lambda v: v[0], str.upper) >>> proc(['hello', 'world']) 'HELLO' - MapCompose
類原型: class scrapy.loader.processors.MapCompose(functions,*default_loader_context)
和Compose類似,也是用給定的多個(gè)方法的組合來構(gòu)造處理器,不同的是內(nèi)部結(jié)果在方法見傳遞的方式:- 該處理器的輸入值被迭代,并且第一函數(shù)被應(yīng)用于每個(gè)元素。這些函數(shù)調(diào)用的結(jié)果(每個(gè)元素一個(gè))被連接以構(gòu)造新的迭代,然后用于應(yīng)用第二個(gè)函數(shù),等等,直到最后一個(gè)函數(shù)被應(yīng)用于收集的值列表的每個(gè)值遠(yuǎn)。最后一個(gè)函數(shù)的輸出值被連接在一起以產(chǎn)生該處理器的輸出。
- 每個(gè)特定函數(shù)可以返回值或值列表,這些值通過應(yīng)用于其他輸入值的相同函數(shù)返回的值列表展平。函數(shù)也可以返回None,在這種情況下,該函數(shù)的輸出將被忽略,以便在鏈上進(jìn)行進(jìn)一步處理。
- 此處理器提供了一種方便的方法來組合只使用單個(gè)值(而不是iterables)的函數(shù)。由于這個(gè)原因, MapCompose處理器通常用作輸入處理器,因?yàn)閿?shù)據(jù)通常使用選擇器的 extract()方法提取,選擇器返回unicode字符串的列表。
>>> def filter_world(x): ... return None if x == 'world' else x ... >>> from scrapy.loader.processors import MapCompose >>> proc = MapCompose(filter_world, unicode.upper) >>> proc([u'hello', u'world', u'this', u'is', u'scrapy']) [u'HELLO, u'THIS', u'IS', u'SCRAPY'] - SelectJmes
類原型: class scrapy.loader.processors.SelectJmes(json_path)
使用提供給構(gòu)造函數(shù)的json路徑查詢值,并返回輸出。需要運(yùn)行jmespath。該處理器一次只需要一個(gè)輸入。
和json一起使用>>> from scrapy.loader.processors import SelectJmes, Compose, MapCompose >>> proc = SelectJmes("foo") #for direct use on lists and dictionaries >>> proc({'foo': 'bar'}) 'bar' >>> proc({'foo': {'bar': 'baz'}}) {'bar': 'baz'}>>> import json >>> proc_single_json_str = Compose(json.loads, SelectJmes("foo")) >>> proc_single_json_str('{"foo": "bar"}') u'bar' >>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo'))) >>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]') [u'bar']
2.3 Item Pipeline
前面已經(jīng)說了Item Pipeline的基本用法了,現(xiàn)在看的是如何利用item Pipeline來進(jìn)行數(shù)據(jù)存儲(chǔ)
2.3.1內(nèi)置圖片和文件下載
item Pipeline提供了一些可以重用的Pipeline,其中有filesPipeline和imagesPipeline,他們都實(shí)現(xiàn)了以下特性:
- 避免重新下載最近已經(jīng)下載過的數(shù)據(jù)
- 指定存儲(chǔ)的位置和方式
此外,imagesPipeline還提供了二位的特性:
- 將所有下載的圖片轉(zhuǎn)換成通用的格式(jpg)和模式(rgb)
- 粗略圖生成
- 檢測圖像的寬/高,確保他們滿足最小限制
使用FIlesPipeline方法如下:
1)在setting.py的ITEM_PIPELINES中添加一條'scrapy.pipelines.files.FilesPipeline':1
2)在item中添加兩個(gè)字段,比如:
file_urls = scrapy.Field()
files = scrapy.Field()
3)在setting.py文件中添加下載路徑,文件url所在的item字段,和文件結(jié)果信息所在item字段,比如:
FILES_STORE = '/HOME/XXX/SPIDER'
FILES_URLS_FIELD ='file_urls'
FILES_RESULT_FIELD = 'files'
使用files_expires設(shè)置文件過期時(shí)間,示例如下:
FILES_EXPIRES = 30 #30天過期
使用ImagesPIpeline方法如下:
1)在setting.py的ITEM_PIPELINES中添加一條'scrapy.pipelines.images.ImagesPipeline':1
2)在item中添加兩個(gè)字段,比如:
image_urls = scrapy.Field()
images = scrapy.Field()
3 )在setting.py文件中添加下載路徑,文件url所在的item字段,和文件結(jié)果信息所在item字段,比如:
IMAGES_STORE = '/HOME/XXX/SPIDER'
IMAGES_URLS_FIELD ='file_urls'
IMAGES_RESULT_FIELD = 'files'
除此之外,使用IMAGES_THUMS制作縮略圖,并這是縮略圖尺寸大小,并使用IMAGES_EXPIRES設(shè)置文件過期時(shí)間,示例如下:
IMAGES_THUMBS = {
'small':(50, 50),
'big': (270,270),
}
IMAGES_EXPIRES = 30 #30天過期
除此之外,Item Pipeline還有三個(gè)方法非常常用
- open_spider(self, spider)
當(dāng)spider被開啟時(shí),這個(gè)方法被調(diào)用 - close_spider(self, spider)
當(dāng)spider被關(guān)閉是,這個(gè)方法被調(diào)用 - from_crawler(cls, crawler)
這個(gè)類方法從Crawles屬性中創(chuàng)建一個(gè)pipeline實(shí)例,Crawle對(duì)象能夠接觸所有Scrapy的核心組件,比如setting
以下是一個(gè)存儲(chǔ)到MongoDB的示例代碼:
import pymongo
class ZhihuspiderPipeline(object):
def __init__(self, mongo_url, mongo_db):
self.mongo_url = mongo_url
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_url=crawler.settings.get('MONGO_URL'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_url)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db.UserInfo.insert(dict(item))
return item
代碼中首先通過from_crawler方法,獲取setting中的MongoDB的URL和數(shù)據(jù)庫名稱,從而創(chuàng)建一個(gè)MongoPIpeline實(shí)例,在SPider開始運(yùn)行是,在open_spider方法中建立數(shù)據(jù)庫連接,當(dāng)Spider關(guān)閉是,在close_spider方法中關(guān)閉數(shù)據(jù)庫連接。
當(dāng)是,這里配置的Pipeline會(huì)作用于所有的Spider,假如項(xiàng)目中有很多Spider在運(yùn)行,處理起來就會(huì)很麻煩。所以通過process_item(self,item,spider)中的SPider參數(shù)判斷是來自于哪個(gè)爬蟲,不過還有更好的方法:
就是配置SPider類中的suctom_settings對(duì)象,為每一個(gè)Spider配置不同的pipeline
class MySpider(CrawlSpider):
custom_settings = {
'ITEM_PIPELINES':{
'test.pipelines.TestPipeline.TestPipeline':1,
}
}
2.4 請(qǐng)求與響應(yīng)
在編寫Spider的模塊中接觸最緊密的是請(qǐng)求和響應(yīng)。
2.4.1Request對(duì)象
一個(gè)Request對(duì)象代表著一個(gè)HTTP請(qǐng)求,通常在Spider類中產(chǎn)生,然后傳遞給下載器,最后返回一個(gè)響應(yīng)
類原型:class scrapy.http.Request(url[, callback, method='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback])
構(gòu)造參數(shù)說明:
- url(string) - 此請(qǐng)求的網(wǎng)址
- callback(callable) - 將使用此請(qǐng)求的響應(yīng)(一旦下載)作為其第一個(gè)參數(shù)調(diào)用的函數(shù)。如果請(qǐng)求沒有指定回調(diào),parse()將使用spider的 方法。請(qǐng)注意,如果在處理期間引發(fā)異常,則會(huì)調(diào)用errback。
- method(string) - 此請(qǐng)求的HTTP方法。默認(rèn)為’GET’。
- meta(dict) - 屬性的初始值Request.meta。如果給定,在此參數(shù)中傳遞的dict將被淺復(fù)制。
- body(str或unicode) - 請(qǐng)求體。如果unicode傳遞了a,那么它被編碼為 str使用傳遞的編碼(默認(rèn)為utf-8)。- 如果 body沒有給出,則存儲(chǔ)一個(gè)空字符串。不管這個(gè)參數(shù)的類型,存儲(chǔ)的最終值將是一個(gè)str(不會(huì)是unicode或None)。
- headers(dict) - 這個(gè)請(qǐng)求的頭。dict值可以是字符串(對(duì)于單值標(biāo)頭)或列表(對(duì)于多值標(biāo)頭)。如果 None作為值傳遞,則不會(huì)發(fā)送HTTP頭。
- cookie(dict或list) - 請(qǐng)求cookie。這些可以以兩種形式發(fā)送。
- encoding(string) - 此請(qǐng)求的編碼(默認(rèn)為’utf-8’)。此編碼將用于對(duì)URL進(jìn)行百分比編碼,并將正文轉(zhuǎn)換為str(如果給定unicode)。
- priority(int) - 此請(qǐng)求的優(yōu)先級(jí)(默認(rèn)為0)。調(diào)度器使用優(yōu)先級(jí)來定義用于處理請(qǐng)求的順序。具有較高優(yōu)先級(jí)值的請(qǐng)求將較早執(zhí)行。允許負(fù)值以指示相對(duì)低優(yōu)先級(jí)。
- dont_filter(boolean) - 表示此請(qǐng)求不應(yīng)由調(diào)度程序過濾。當(dāng)您想要多次執(zhí)行相同的請(qǐng)求時(shí)忽略重復(fù)過濾器時(shí)使用。小心使用它,或者你會(huì)進(jìn)入爬行循環(huán)。默認(rèn)為False。
- errback(callable) - 如果在處理請(qǐng)求時(shí)引發(fā)任何異常,將調(diào)用的函數(shù)。這包括失敗的404 HTTP錯(cuò)誤等頁面。
cookies參數(shù)設(shè)置的兩種方法
使用字典request_with_cookies = Request(url="http://www.example.com", cookies={'currency': 'USD', 'country': 'UY'})使用字典列表發(fā)送
request_with_cookies = Request(url="http://www.example.com", cookies=[{'name': 'currency', 'value': 'USD', 'domain': 'example.com', 'path': '/currency'}])當(dāng)某些網(wǎng)站返回Cookie(在響應(yīng)中)時(shí),這些Cookie會(huì)存儲(chǔ)在該域的Cookie中,并在將來的請(qǐng)求中再次發(fā)送。這是任何常規(guī)網(wǎng)絡(luò)瀏覽器的典型行為。但是,如果由于某種原因,想要避免與現(xiàn)有Cookie合并,可以設(shè)置Requ.meta中dont_merge_cookies字段的值。示例如下
request_with_cookies = Request(url="http://www.example.com", cookies={'currency': 'USD', 'country': 'UY'}, meta={'dont_merge_cookies': True})
errback的使用方法示例:
import scrapy from scrapy.spidermiddlewares.httperror import HttpError from twisted.internet.error import DNSLookupError from twisted.internet.error import TimeoutError, TCPTimedOutError class ErrbackSpider(scrapy.Spider): name = "errback_example" start_urls = [ "http://www.httpbin.org/", # HTTP 200 expected "http://www.httpbin.org/status/404", # Not found error "http://www.httpbin.org/status/500", # server issue "http://www.httpbin.org:12345/", # non-responding host, timeout expected "http://www.httphttpbinbin.org/", # DNS error expected ] def start_requests(self): for u in self.start_urls: yield scrapy.Request(u, callback=self.parse_httpbin, errback=self.errback_httpbin, dont_filter=True) def parse_httpbin(self, response): self.logger.info('Got successful response from {}'.format(response.url)) # do something useful here... def errback_httpbin(self, failure): # log all failures self.logger.error(repr(failure)) # in case you want to do something special for some errors, # you may need the failure's type: if failure.check(HttpError): # these exceptions come from HttpError spider middleware # you can get the non-200 response response = failure.value.response self.logger.error('HttpError on %s', response.url) elif failure.check(DNSLookupError): # this is the original request request = failure.request self.logger.error('DNSLookupError on %s', request.url) elif failure.check(TimeoutError, TCPTimedOutError): request = failure.request self.logger.error('TimeoutError on %s', request.url)
下面寫的是Request的子類 FormRquest類,專門用來處理html表單,尤其對(duì)隱藏表單的處理非常方便,適合用來完成登錄操作
類原型:class scrapy.http.FormRequest(url[, formdata, ...])
其中構(gòu)造參數(shù)formdata可以是字典,也可以是可迭代的(key,value)元祖,代表著需要提交的表單數(shù)據(jù)。
示例如下:
return [FormRequest(url="http://www.example.com/post/action",
formdata={'name': 'John Doe', 'age': '27'},
callback=self.after_post)]
通常網(wǎng)站通過<input type="hidden">實(shí)現(xiàn)對(duì)某些表單字段的預(yù)填充,就像知乎的隱藏表單的_xfv數(shù)值,Scray在FromRequest類提供了一個(gè)類方法from_reponse來解決隱藏表單這個(gè)問題。方法原型如下:
classmethod from_response(response[, formname=None, formid=None, formnumber=0, formdata=None, formxpath=None, formcss=None, clickdata=None, dont_click=False, ...])
參數(shù)說明:
- response(Responseobject) - 包含將用于預(yù)填充表單字段的HTML表單的響應(yīng)
- formname(string) - 如果給定,將使用name屬性設(shè)置為此值的形式。
- formid(string) - 如果給定,將使用id屬性設(shè)置為此值的形式。
- formxpath(string) - 如果給定,將使用匹配xpath的第一個(gè)表單。
- formcss(string) - 如果給定,將使用匹配css選擇器的第一個(gè)形式。
- formnumber(integer) - 當(dāng)響應(yīng)包含多個(gè)表單時(shí)要使用的表單的數(shù)量。第一個(gè)(也是默認(rèn))是0。
- formdata(dict) - 要在表單數(shù)據(jù)中覆蓋的字段。如果響應(yīng)元素中已存在字段,則其值將被在此參數(shù)中傳遞的值覆蓋。
- clickdata(dict) - 查找控件被點(diǎn)擊的屬性。如果沒有提供,表單數(shù)據(jù)將被提交,模擬第一個(gè)可點(diǎn)擊元素的點(diǎn)擊。除了html屬性,控件可以通過其相對(duì)于表單中其他提交表輸入的基于零的索引,通過nr屬性來標(biāo)識(shí)。
- dont_click(boolean) - 如果為True,表單數(shù)據(jù)將在不點(diǎn)擊任何元素的情況下提交。
下面是一個(gè)實(shí)現(xiàn)登錄功能的示例代碼:
import scrapy
class LoginSpider(scrapy.Spider):
name = 'example.com'
start_urls = ['http://www.example.com/users/login.php']
def parse(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={'username': 'john', 'password': 'secret'},
callback=self.after_login
)
def after_login(self, response):
# check login succeed before going on
if "authentication failed" in response.body:
self.logger.error("Login failed")
return
2.4.2 Response對(duì)象
Response對(duì)象代表著HTTP響應(yīng),Response通常是從下載器獲取然后交給Spider處理:
類原型:class scrapy.http.Response(url[, status=200, headers=None, body=b'', flags=None, request=None])
構(gòu)造參數(shù)說明:
- url(string) - 此響應(yīng)的URL
- status(integer) - 響應(yīng)的HTTP狀態(tài)。默認(rèn)為200。
- headers(dict) - 這個(gè)響應(yīng)的頭。dict值可以是字符串(對(duì)于單值標(biāo)頭)或列表(對(duì)于多值標(biāo)頭)。
- body(str) - 響應(yīng)體。它必須是str,而不是unicode,除非你使用一個(gè)編碼感知響應(yīng)子類,如 TextResponse。
- flags(list) - 是一個(gè)包含屬性初始值的 Response.flags列表。如果給定,列表將被淺復(fù)制。
- request(Requestobject) - 屬性的初始值Response.request。這代表Request生成此響應(yīng)。
- meta(dict):用來初始化Response.meta
關(guān)于參數(shù)的其他補(bǔ)充:
- request
Request生成此響應(yīng)的對(duì)象。在響應(yīng)和請(qǐng)求通過所有下載中間件后,此屬性在Scrapy引擎中分配。特別地,這意味著:
HTTP重定向?qū)?dǎo)致將原始請(qǐng)求(重定向之前的URL)分配給重定向響應(yīng)(重定向后具有最終URL)。
Response.request.url并不總是等于Response.url
此屬性僅在爬蟲程序代碼和 Spider Middleware中可用,但不能在Downloader Middleware中使用(盡管您有通過其他方式可用的請(qǐng)求)和處理程序response_downloaded。 - meta
的快捷方式Request.meta的屬性 Response.request對(duì)象(即self.request.meta)。
與Response.request屬性不同,Response.meta 屬性沿重定向和重試傳播,因此您將獲得Request.meta從您的爬蟲發(fā)送的原始屬性。
也可以看看
Request.meta 屬性 - flags
包含此響應(yīng)的標(biāo)志的列表。標(biāo)志是用于標(biāo)記響應(yīng)的標(biāo)簽。例如:’cached’,’redirected ‘等等。它們顯示在Response(_ str_ 方法)的字符串表示上,它被引擎用于日志記錄。 - copy()
返回一個(gè)新的響應(yīng),它是此響應(yīng)的副本。 - replace([ url,status,headers,body,request,flags,cls ] )
返回具有相同成員的Response對(duì)象,但通過指定的任何關(guān)鍵字參數(shù)賦予新值的成員除外。該屬性Response.meta是默認(rèn)復(fù)制。 - urljoin(url )
通過將響應(yīng)url與可能的相對(duì)URL 組合構(gòu)造絕對(duì)url。
這是一個(gè)包裝在urlparse.urljoin,它只是一個(gè)別名,使這個(gè)調(diào)用:
urlparse.urljoin(response.url, url)
同樣的,Response有一個(gè)子類TextResponse,多了一個(gè)智能編碼的功能。
類原型:class scrapy.http.TextResponse(url[, encoding[, ...]])
構(gòu)造參數(shù)encoding是一個(gè)包含編碼的字符串,如果使用一個(gè)unicode編碼的body構(gòu)造出TextResponse實(shí)例,那body屬性會(huì)使用encoding進(jìn)行編碼。
TextResponse類除了具有Response屬性,還擁有自己的屬性:
- encoding:包含編碼的字符串。編碼的優(yōu)先級(jí)由高到低如下所示:
1)首先選用構(gòu)造器中傳入的encoding
2)選用http頭中Content-Type字段的編碼。如果編碼無效,則被忽略,繼續(xù)嘗試下面的規(guī)則
3)選用響應(yīng)body中的編碼
4)最后猜測響應(yīng)的編碼,這種方式是比較脆弱 - selector:以當(dāng)前響應(yīng)為目標(biāo)的選擇器實(shí)例。
TextResponse類除了支持Response中的方法,還支持以下方法response - body_as_unicode():返回unicode編碼的響應(yīng)body內(nèi)容。等價(jià)于:
response.body.decode(response.encoding) - xpath(query):等價(jià)于TextResponse.selector.xpath(query).示例如下:
response.xpath('//p') - css(query):等價(jià)于TextResponse.selector.css(query).示例如下:
response.css('//p')
2.5下載器中間件
下載器中間件是介于Scrapy的request/response處理的鉤子框架,是用于全局修改Scrapy的request和response,可以幫助我們定制自己的爬蟲系統(tǒng)
激活哦下載器中間件的方法如下
在setting.py里面的DOWNLOADER_MIDDLEWARES修改,示例如下:
DOWNLOADER_MIDDLEWARES = {
‘myproject.middlewares.CustomDownloaderMiddleware': 543,
}
后面數(shù)值的設(shè)置要看自己的需求,越接近0越靠近引擎,越接近1000越靠近下載器,數(shù)字設(shè)置一般看自己需要先用到哪些中間件,就把數(shù)字設(shè)定得比哪個(gè)中間件大
中間件組建定義了以下python類,可以根據(jù)這些類來編寫自己的中間件
- process_request(request,spider)
- process_response(request,response,spider)
- process_exception(request,exception,spider)
2.5.1process_request(request,spider)
當(dāng)每個(gè)request通過下載中間件時(shí),該方法被調(diào)用,返回值必須為none,response對(duì)象,request對(duì)象中的一個(gè)或raise IgnoreRequest異常
參數(shù)說明:
Request:處理的request
Spider:該Request對(duì)應(yīng)的Spider
返回值:如果返回None,Scrapy將繼續(xù)處理該Request,執(zhí)行其他中間件的相應(yīng)方法,直到合適的下載器處理函數(shù)被調(diào)用,該Request被執(zhí)行(其Response下載)
如果返回Response對(duì)象,SCrapy不會(huì)調(diào)用下面2個(gè)方法,或相應(yīng)的下載方法,將返回該response。已安裝的中間件的process_response()方法則會(huì)在每個(gè)respon返回時(shí)被調(diào)用
如果返回request對(duì)象,Scrapy則停止調(diào)用process_request方法并重新調(diào)度放回的Request。當(dāng)新返回的Request被執(zhí)行后,相應(yīng)地中間件鏈將會(huì)根據(jù)下載的Response被調(diào)用
如果是Raise IgnoreRequest異常,則安裝的下載中間件的process_exception()方法會(huì)被調(diào)用。如果沒有任何一個(gè)方法處理該異常,則Request的errback方法會(huì)被調(diào)用。如果沒有代碼處理拋出的異常,則該異常被忽略且不記錄
2.5.2process_request(request,response,spider)
該方法主要用來處理產(chǎn)生的Response返回值必須為none,response對(duì)象,request對(duì)象中的一個(gè)或raise IgnoreRequest異常
參數(shù)說明:
Request:處理的request
response:處理的response
Spider:該Request對(duì)應(yīng)的Spider
返回值:
如果返回Response對(duì)象,可以與傳入的respon相同,也可以是新的對(duì)象,該Response會(huì)被鏈中的其他中間見的process_reponse()方法處理
如果返回request對(duì)象,Scrapy則停止調(diào)用process_request方法并重新調(diào)度放回的Request。當(dāng)新返回的Request被執(zhí)行后,相應(yīng)地中間件鏈將會(huì)根據(jù)下載的Response被調(diào)用
如果是Raise IgnoreRequest異常,則安裝的下載中間件的process_exception()方法會(huì)被調(diào)用。如果沒有任何一個(gè)方法處理該異常,則Request的errback方法會(huì)被調(diào)用。如果沒有代碼處理拋出的異常,則該異常被忽略且不記錄
2.5.3process_request(request,exceptionmspider)
當(dāng)下載處理器或process_request()拋出異常,比如IgnoreRequest異常時(shí),Scrapy調(diào)用process_exception().process_exception()應(yīng)該返回none,response對(duì)象,request對(duì)象中的一個(gè)
參數(shù)說明:
Request:處理的request
exception:拋出的異常
Spider:該Request對(duì)應(yīng)的Spider
返回值:
如果返回None,Scrapy將會(huì)繼續(xù)處理該異常,接著調(diào)用已安裝的其他中間件的process_exception()方法,知道所有中間件都被調(diào)用完畢,則調(diào)用默認(rèn)的異常處理
如果返回response對(duì)象,則已安裝的中間級(jí)鏈的process_response()方法被調(diào)用。Scrapy將不會(huì)調(diào)用任何其他中間件的process_exception()方法
如果返回Request對(duì)象,則返回的request將會(huì)被重新調(diào)用下載,這將停止中間級(jí)的process_exception()方法執(zhí)行,類似于返回respon對(duì)象的處理
下面是自定義下載器中間件的兩個(gè)栗子:
設(shè)置User-Agent字段,需要自己在setting,py中設(shè)置User-Agent列表
class RandomUserAgent(object):
def __init__(self,agents):
self.agents =agents
@classmethod
def from_crawler(cls,crawler):
return cls(crawler.settings.getlist('USER_AGENTS'))
def process_request(self,request,spider):
request.headers.setdefault('User-Agent', random.choice(self.agents))
動(dòng)態(tài)設(shè)置request代理ip,需要自己在setting,py中設(shè)置IPlist列表
class RandomProxy(object):
def __init__(self,iplist):
self.iplist=iplist
@classmethod
def from_crawler(cls,crawler):
return cls(crawler.settings.getlist('IPLIST'))
def process_request(self, request, spider):
proxy = random.choice(self.iplist)
request.meta['proxy'] =proxy
2.6Spider中間件
這個(gè)看官方文檔就好了 ——這里
3.部署
這里都是編寫完爬蟲代碼后的其他一些應(yīng)用,不完全屬于部署
3.1分布式下載器:Crawlea
官方提供了一個(gè)分布式下載器,用來幫助我們躲避反爬蟲的措施。首先在官網(wǎng)注冊(cè)。之后拿到APIKEY用作驗(yàn)證
不過是需要錢的
官網(wǎng):https://app.scrapinghub.com/account/login/?next=/next=/account/login
安裝:pip install scrapy-crawlera
之后修改setting.py
DOWNLOADER_MIDDLEWARES ={'scrapy_crawlera.CrawleraMiddleware':300}
CRAWLERA_ENABLED = True
CRAWLERA_APIKEY = '<API key>'
3.2Scrapyd
是的。官方又提供了一個(gè)部署爬蟲非常有用的工具,Scrapyd是運(yùn)行Scrapy爬蟲的服務(wù)程序,它吃屎以http命令方式通過json api進(jìn)行發(fā)布、刪除、啟動(dòng)、停止爬蟲程序的操作,而且它可以同時(shí)管理多個(gè)爬蟲,每個(gè)爬蟲還可以有多個(gè)版本,也是部署分布式爬蟲有效手段
安裝:pipi install scrapyd
使用:輸入scrapyd啟動(dòng),在瀏覽器輸入:http://127.0.0.1:6800/就可以看到界面了
API使用示例如下:
# -*- coding: utf-8 -*-
import requests
import json
baseUrl ='http://127.0.0.1:6800/'
daemUrl ='http://127.0.0.1:6800/daemonstatus.json'
listproUrl ='http://127.0.0.1:6800/listprojects.json'
listspdUrl ='http://127.0.0.1:6800/listspiders.json?project=%s'
listspdvUrl= 'http://127.0.0.1:6800/listversions.json?project=%s'
listjobUrl ='http://127.0.0.1:6800/listjobs.json?project=%s'
delspdvUrl= 'http://127.0.0.1:6800/delversion.json'
#http://127.0.0.1:6800/daemonstatus.json
#查看scrapyd服務(wù)器運(yùn)行狀態(tài)
r= requests.get(daemUrl)
print '1.stats :\n %s \n\n' %r.text
#http://127.0.0.1:6800/listprojects.json
#獲取scrapyd服務(wù)器上已經(jīng)發(fā)布的工程列表
r= requests.get(listproUrl)
print '1.1.listprojects : [%s]\n\n' %r.text
if len(json.loads(r.text)["projects"])>0 :
project = json.loads(r.text)["projects"][0]
#http://127.0.0.1:6800/listspiders.json?project=myproject
#獲取scrapyd服務(wù)器上名為myproject的工程下的爬蟲清單
listspd=listspd % project
r= requests.get(listspdUrl)
print '2.listspiders : [%s]\n\n' %r.text
if json.loads(r.text).has_key("spiders")>0 :
spider =json.loads(r.text)["spiders"][0]
#http://127.0.0.1:6800/listversions.json?project=myproject
##獲取scrapyd服務(wù)器上名為myproject的工程下的各爬蟲的版本
listspdvUrl=listspdvUrl % project
r = requests.get(listspdvUrl)
print '3.listversions : [%s]\n\n' %rtext
if len(json.loads(r.text)["versions"])>0 :
version = json.loads(r.text)["versions"][0]
#http://127.0.0.1:6800/listjobs.json?project=myproject
#獲取scrapyd服務(wù)器上的所有任務(wù)清單,包括已結(jié)束,正在運(yùn)行的,準(zhǔn)備啟動(dòng)的。
listjobUrl=listjobUrl % proName
r=requests.get(listjobUrl)
print '4.listjobs : [%s]\n\n' %r.text
#schedule.json
#http://127.0.0.1:6800/schedule.json -d project=myproject -d spider=myspider
#啟動(dòng)scrapyd服務(wù)器上myproject工程下的myspider爬蟲,使myspider立刻開始運(yùn)行,注意必須以post方式
schUrl = baseurl + 'schedule.json'
dictdata ={ "project":project,"spider":spider}
r= reqeusts.post(schUrl, json= dictdata)
print '5.1.delversion : [%s]\n\n' %r.text
#http://127.0.0.1:6800/delversion.json -d project=myproject -d version=r99'
#刪除scrapyd服務(wù)器上myproject的工程下的版本名為version的爬蟲,注意必須以post方式
delverUrl = baseurl + 'delversion.json'
dictdata={"project":project ,"version": version }
r= reqeusts.post(delverUrl, json= dictdata)
print '6.1.delversion : [%s]\n\n' %r.text
#http://127.0.0.1:6800/delproject.json -d project=myproject
#刪除scrapyd服務(wù)器上myproject工程,注意該命令會(huì)自動(dòng)刪除該工程下所有的spider,注意必須以post方式
delProUrl = baseurl + 'delproject.json'
dictdata={"project":project }
r= reqeusts.post(delverUrl, json= dictdata)
print '6.2.delproject : [%s]\n\n' %r.text
3.3Scrapyd-client
懶得截圖訪問這里查看