scrapy
(異步框架):pip install scrapy
a. pip3 install wheel b. 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted # Twisted?17.1.0?cp35?cp35m?win_amd64.whl; Python是3.5版本的就選擇cp35下載 c. 進入下載目錄,執(zhí)行 pip3 install Twisted?17.1.0?cp35?cp35m?win_amd64.whl # 安裝失敗可能是這個文件的版本導(dǎo)致的,即使Python版本都是對的,可以重新下載一個32位的試試 # 還安裝失敗的話就下載其python版本的,總有一個能成功 d. pip3 install pywin32 e. pip3 install scrapy
安裝完成后,輸入``scrapy`測試一下,出現(xiàn)如下圖顯示,即安裝成功。
scrapy startprojct proNmame
cd proNmame
進入到工程目錄下執(zhí)行爬蟲文件
proName # 工程名字 spiders # 爬蟲包(文件夾) __init__.py __init__.py items.py middlewares.py pipelines.py settings.py # 創(chuàng)建好的工程的配置文件scrapy.cfg # scrapy的配置文件,不用修改
scrapy genspider spiderName www.xxx.com
網(wǎng)址后期可以修改spiders
包下創(chuàng)建一個py文件# -*- coding: utf-8 -*-import scrapyclass FirstSpider(scrapy.Spider): # scrapy.Spider所有爬蟲類的父類 # name表示的爬蟲文件的名稱,當(dāng)前爬蟲文件的唯一標(biāo)識 name = 'first' # 允許的域名,通常會注釋掉 # allowed_domains = ['www.xx.com'] # 起始的url列表,最開始要爬的網(wǎng)址列表 # 作用:可以將內(nèi)部的列表元素進行g(shù)et請求的發(fā)送 start_urls = ['http://www.sougou.com/','www.baidu.com'] # 調(diào)用parse方法解析數(shù)據(jù),方法調(diào)用的次數(shù)由start_urls列表元素個數(shù)決定的 def parse(self, response): # response表示一個響應(yīng)對象, pass
UA偽裝
robots協(xié)議的不遵從
在settings.py
中將ROBOTSTXT_OBEY = True
修改為False
指定日志等級
在settings.py
中添加LOG_LEVEL = 'ERROR'
scrapy crawl spiderName
執(zhí)行工程是不展示日志文件
scrapy crawl spiderName --nolog
這種方式下程序報錯,不會展示;設(shè)置好日志等級后直接執(zhí)行工程即可。
response.xpath('xpath表達(dá)式')
與etree
的不同之處:
取文本/屬性:返回的是一個Selector
對象,文本數(shù)據(jù)是存儲在該對象中
Selector對象[0].extract()
返回字符串Selector對象.extract_first()
返回字符串Selector對象.extract()
返回列表Selector對象.extract_first()
,返回字符串Selector對象.extract()
,返回列表,列表里裝的是字符串spiderName.py
文件
# -*- coding: utf-8 -*-import scrapyclass DuanziSpider(scrapy.Spider): name = 'duanzi' # allowed_domains = ['www.xx.com'] start_urls = ['https://duanziwang.com/'] def parse(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath表達(dá)式解析 for article in article_list: title = article.xpath('./div[1]/h1/a/text()')[0] # 返回一個Selector對象 # <Selector xpath='./div[1]/h1/a/text()' data='關(guān)于健康養(yǎng)生、延年益壽的生活諺語_段子網(wǎng)收錄最新段子'> title = article.xpath('./div[1]/h1/a/text()')[0].extract() # 返回字符串 # 關(guān)于健康養(yǎng)生、延年益壽的生活諺語_段子網(wǎng)收錄最新段子 title = article.xpath('./div[1]/h1/a/text()').extract_first() # 返回字符串 # 關(guān)于健康養(yǎng)生、延年益壽的生活諺語_段子網(wǎng)收錄最新段子 title = article.xpath('./div[1]/h1/a/text()').extract() # 返回列表 # ['關(guān)于健康養(yǎng)生、延年益壽的生活諺語_段子網(wǎng)收錄最新段子'] print(title) break
只可以將parse方法的返回值存儲到指定后綴的文本文件中
指定后綴:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
,通常用csv
指令scrapy crawl spiderName -o filePath
# -*- coding: utf-8 -*-import scrapyclass DuanziSpider(scrapy.Spider): name = 'duanzi' # allowed_domains = ['www.xx.com'] start_urls = ['https://duanziwang.com/'] # 基于終端指令的持久化存儲 def parse(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath表達(dá)式解析 all_data = [] for article in article_list: title = article.xpath('./div[1]/h1/a/text()').extract_first() content = article.xpath('./div[2]/p//text()').extract() content = ''.join(content) dic = { "title": title, "content": content } all_data.append(dic) return all_data# 終端指令# scrapy crawl spiderName -o duanzi.csv
? scrapy
建議使用管道持久化存儲
數(shù)據(jù)解析(spiderName .py
)
實例化item類型對象(items.py
)
在items.py
的item類中定義相關(guān)的屬性
fieldNmae = scrapy.Field()
將解析的數(shù)據(jù)存儲封裝到item類型的對象中(spiderName .py
)
item['fileName'] = value
給item對象的fieldNmae屬性賦值
將item對象提交給(spiderName .py
)
yield item
將item提交給優(yōu)先級最高的管道
在管道中接收item,可以將item中存儲的數(shù)據(jù)進行任意形式的持久化存儲(pipelines.py
)
process_item()
:負(fù)責(zé)接收item對象且對其進行持久化存儲
在配置文件settings.py
中開啟管道機制
找到如下代碼,取消注釋
ITEM_PIPELINES = { # 300表示的是優(yōu)先級,數(shù)值越小,優(yōu)先級越高 'duanziPro.pipelines.DuanziproPipeline': 300,}
案例:將文本數(shù)據(jù)持久化存儲
按上述在settings.py
找到管道代碼,取消注釋。
spiderName .py
# -*- coding: utf-8 -*-import scrapyfrom duanziPro.items import DuanziproItemclass DuanziSpider(scrapy.Spider): name = 'duanzi' # allowed_domains = ['www.xx.com'] start_urls = ['https://duanziwang.com/'] # 基于管道的持久化存儲 def parse(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath表達(dá)式解析 for article in article_list: title = article.xpath('./div[1]/h1/a/text()').extract_first() content = article.xpath('./div[2]/pre/code//text()').extract() content = ''.join(content) print(content) # 實例化item對象 item = DuanziproItem() # 通過中括號的形式訪問屬性給其賦值 item['title'] = title item['content'] = content # 向管道提交item yield item
items.py
import scrapyclass DuanziproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 使用固有屬性定義了兩個屬性 # Field是一個萬能數(shù)據(jù)類型 title = scrapy.Field() content = scrapy.Field()
pipelines.py
class DuanziproPipeline(object): # 重寫父類的該方法:該方法只會在爬蟲開始的時候執(zhí)行一次 fp = None # 打開文件 def open_spider(self, spider): print('open spider') self.fp = open('./duanzi.txt', 'w', encoding='utf-8') # 關(guān)閉文件 def close_spider(self, spider): print('close spider') self.fp.close() # 接收爬蟲文件返回item對象,process_item方法每調(diào)用一次可接收一個item對象 # item參數(shù):接收到的某一個item對象 def process_item(self, item, spider): # 取值 title = item['title'] content = item['content'] self.fp.write(title + ":" + content + "\n") return item
管道文件中的管道類表示的是什么?
一個管道類對應(yīng)的就是一種存儲形式(文本文件,數(shù)據(jù)庫)
如果想要實現(xiàn)數(shù)據(jù)備份,則需要使用多個管道類(多種存儲形式:MySQL,Redis)
process_item中的 retutn item
:
將item傳遞給下一個即將被執(zhí)行(按照配置文件中ITEM_PIPELINES得權(quán)重排序)的管道類
在pipelines.py
中添加如下代碼
import pymysqlclass MysqlPipeline(object): conn = None cursor = None def open_spider(self, spider): self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123', db='spider', charset='utf8') def process_item(self, item, spider): # 取值 title = item['title'] content = item['content'] self.cursor = self.conn.cursor() # sql語句 sql = 'insert into duanzi values ("%s","%s")' % (title, content) try: self.cursor.execute(sql) self.conn.commit() except Exception as e: print(e) self.conn.rollback() return item def close_spider(self, spider): self.cursor.close() self.conn.close()
在settings.py
中將MysqlPipeline類注冊到ITEM_PIPELINES中
ITEM_PIPELINES = { # 300表示的是優(yōu)先級,數(shù)值越小,優(yōu)先級越高 'duanziPro.pipelines.DuanziproPipeline': 300, 'duanziPro.pipelines.MysqlPipeline': 301,}
因為redis有的版本不支持存儲字典,下載2.10.6版本
pip install redis==2.10.6
在pipelines.py
中添加如下代碼
from redis import Redisclass RedisPipeline(object): conn = None def open_spider(self, spider): self.conn = Redis(host='127.0.0.1', port=6379, password='yourpassword') def process_item(self, item, spider): self.conn.lpush('duanziList', item) # 報錯:因為redis有的版本不支持存儲字典,pip install redis==2.10.6
在settings.py
中將RedisPipeline類注冊到ITEM_PIPELINES中
ITEM_PIPELINES = { # 300表示的是優(yōu)先級,數(shù)值越小,優(yōu)先級越高 'duanziPro.pipelines.DuanziproPipeline': 300, 'duanziPro.pipelines.RedisPipeline': 301,}
可以在start_urls這個列表中添加url,但是比較繁瑣
get請求發(fā)送
yield scrapy.Request(url,callback)
post請求發(fā)送
yield scrapy.FormRequest(url,callback,formdata)
父類中start_requests請求發(fā)送的原理
# 簡單模擬父類的方法,主要看yielddef start_requests(self): for url in self.start_urls: # 發(fā)起get請求 yield scrapy.Request(url=url,callback=self.parse) # 發(fā)起post請求,formdata存放請求參數(shù) yield scrapy.FormRequest(url=url,callback=self.parse,formdata={})
主要是在spiderName .py
中使用遞歸方法,且明確遞歸結(jié)束的條件;
使用父類yield實現(xiàn)全站爬取
# -*- coding: utf-8 -*-import scrapyfrom duanziPro.items import DuanziproItemclass DuanziSpider(scrapy.Spider): name = 'duanzi' # allowed_domains = ['www.xx.com'] start_urls = ['https://duanziwang.com/'] # 手動請求的發(fā)送,對其他頁碼的數(shù)據(jù)進行請求操作 # 定義通用url模板 url = "https://duanziwang.com/page/%d/" pageNum = 2 def parse(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') # 基于xpath表達(dá)式解析 all_data = [] for article in article_list: title = article.xpath('./div[1]/h1/a/text()').extract_first() content = article.xpath('./div[2]/pre/code//text()').extract() content = ''.join(content) # 實例化item對象 item = DuanziproItem() # 通過中括號的形式訪問屬性給其賦值 item['title'] = title item['content'] = content # 向管道提交item yield item if self.pageNum < 5: new_url = format(self.url%self.pageNum) self.pageNum += 1 # 遞歸實現(xiàn)全站數(shù)據(jù)爬取,callback指定解析的方法 yield scrapy.Request(url=new_url, callback=self.parse)
pipelines.py
中實現(xiàn)數(shù)據(jù)持久化存儲class DuanziproPipeline(object): # 重寫父類的該方法:該方法只會在爬蟲開始的時候執(zhí)行一次 fp = None def open_spider(self, spider): print('open spider') self.fp = open('./duanzi.txt', 'w', encoding='utf-8') # 關(guān)閉fp def close_spider(self, spider): print('close spider') self.fp.close() # 接收爬蟲文件返回item對象,process_item方法每調(diào)用一次可接收一個item對象 # item參數(shù):接收到的某一個item對象 def process_item(self, item, spider): # 取值 title = item['title'] content = item['content'] self.fp.write(title + ":" + content + "\n") # 將item轉(zhuǎn)交給下一個即將被執(zhí)行的管道類 return item
settings.py
中開啟管道類ITEM_PIPELINES = { # 300表示的是優(yōu)先級,數(shù)值越小,優(yōu)先級越高 'duanziPro.pipelines.DuanziproPipeline': 300,}
向管道中提交item對象
yield item
手動請求發(fā)送
yield scrapy.Request(url,callback)
引擎(Scrapy Engine)
處理整個系統(tǒng)的數(shù)據(jù)流,觸發(fā)事物(框架核心)。
調(diào)度器(Scheduer)
用來接收引擎發(fā)過來的請求,壓入隊列中,并在引擎再次請求的時候返回。
下載器(Downloader)
用于下載網(wǎng)頁內(nèi)容,并將網(wǎng)頁內(nèi)容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效模型上的)。
爬蟲(Spiders)
爬蟲主要是干活的,用于從特定的網(wǎng)頁中提取自己需要的信息,即所謂的實體(item)。用戶也可以從中提取出鏈接,讓Scrapy繼續(xù)抓取下一個頁面
管道(item Pipeline)
負(fù)責(zé)處理爬蟲從網(wǎng)頁抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當(dāng)頁面被爬蟲解析后,將被發(fā)送到項目管道,并經(jīng)過幾個特定的次序處理數(shù)據(jù)。
首先執(zhí)行爬蟲文件spider,spider的作用是
(1)解析(2)發(fā)請求,原始的url存儲在于spider中
1:當(dāng)spider執(zhí)行的時候,首先對起始的url發(fā)送請求,將起始url封裝成請求對象
2:將請求對象傳遞給引擎
3:引擎將請求對象傳遞給調(diào)度器(內(nèi)部含有隊列和過濾器兩個機制),調(diào)度器將請求存儲在隊列(先進先出)中
4:調(diào)度器從隊列中調(diào)度出url的相應(yīng)對象再將請求傳遞給引擎
5:引擎將請求對象通過下載中間件發(fā)送給下載器
6:下載器拿到請求到互聯(lián)網(wǎng)上去下載
7:互聯(lián)網(wǎng)將下載好的數(shù)據(jù)封裝到響應(yīng)對象給到下載器
8:下載器將響應(yīng)對象通過下載中間件發(fā)送給引擎
9:引擎將封裝了數(shù)據(jù)的響應(yīng)對象回傳給spider類parse方法中的response對象
10:spider中的parse方法被調(diào)用,response就有了響應(yīng)值
11:在spider的parse方法中進行解析代碼的編寫;
(1)會解析出另外一批url,(2)會解析出相關(guān)的文本數(shù)據(jù)
12: 將解析拿到的數(shù)據(jù)封裝到item中
13:item將封裝的文本數(shù)據(jù)提交給引擎
14:引擎將數(shù)據(jù)提交給管道進行持久化存儲(一次完整的請求數(shù)據(jù))
15:如果parder方法中解析到的另外一批url想繼續(xù)提交可以繼續(xù)手動進行發(fā)請求
16:spider將這批請求對象封裝提交給引擎
17:引擎將這批請求對象發(fā)配給調(diào)度器
16:這批url通過調(diào)度器中過濾器過濾掉重復(fù)的url存儲在調(diào)度器的隊列中
17:調(diào)度器再將這批請求對象進行請求的調(diào)度發(fā)送給引擎
引擎作用:
1:處理流數(shù)據(jù) 2:觸發(fā)事物
引擎根據(jù)相互的數(shù)據(jù)流做判斷,根據(jù)拿到的流數(shù)據(jù)進行下一步組件中方法的調(diào)用
下載中間件: 位于引擎和下載器之間,可以攔截請求和響應(yīng)對象;攔截到請求和響應(yīng)對象后可以
篡改頁面內(nèi)容和請求和響應(yīng)頭信息。
爬蟲中間件:位于spider和引擎之間,也可以攔截請求和響應(yīng)對象,不常用。
作用
實現(xiàn)深度爬取。
深度爬取
爬取的數(shù)據(jù)不在同一張頁面中。
在進行手動發(fā)送請求的時候,可以將一個meta字典傳遞給callback指定的回調(diào)函數(shù)
yield scrapy.Request(url,callback,meta={})
在回調(diào)函數(shù)中接收meta
response.meta['key']
將meta字典中key對應(yīng)的value值取出
spiderName.py
# -*- coding: utf-8 -*-import scrapyfrom moviePro.items import MovieproItemclass MovieSpider(scrapy.Spider): name = 'movie' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567kan.com/frim/index1.html'] # 通用url url = 'https://www.4567kan.com/frim/index1-%d.html' pagNum = 2 # 解析首頁的數(shù)據(jù),爬取電影名稱和簡介 def parse(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: mv_name = li.xpath('./div/a/@title').extract_first() item = MovieproItem() item['name'] = mv_name detail_url = "https://www.4567kan.com/" + li.xpath('./div/a/@href').extract_first() yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item}) # meta 就是一個字典,可以將字典傳給callback指定的回調(diào)函數(shù),實現(xiàn)請求傳參 # 全站信息的爬取,測試前4頁 if self.pagNum < 5: new_url = format(self.url % self.pagNum) self.pagNum += 1 yield scrapy.Request(url=new_url,callback=self.parse) # 自定義解析方法,解析詳情頁電影簡介 def parse_detail(self, response): desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[3]//text()').extract_first() # 接收請求傳參傳遞過來的item item = response.meta['item'] item['desc'] = desc yield item
位于spider和引擎之間,也可以攔截請求和響應(yīng)對象,不常用。
位于引擎和下載器之間,可以攔截請求和響應(yīng)對象;攔截到請求和響應(yīng)對象后可以
篡改頁面內(nèi)容和請求和響應(yīng)頭信息。
作用:攔截所有請求和響應(yīng)
UA偽裝(篡改請求頭信息)
process_request()
方法中,
request.headers['User-Agent'] ="請求頭信息"
設(shè)置代理
process_exception()
方法中,
request.meta['proxy'] = 'https://ip:prot'
return request
工程項目中middlewares.py
就是中間件。
from scrapy import signalsimport random# UA池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", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]PROXY_HTTP = ["ip:port"]PROXY_HTTPS = ["ip:port"]# 下載中間件class MiddleproDownloaderMiddleware(object): def process_request(self, request, spider): """ 攔截正常請求 :param request: 攔截到的正常請求 :param spider: 爬蟲類實例化的對象 :return: """ # UA偽裝 request.headers['User-Agent'] = random.choice(user_agent_list) return None def process_response(self, request, response, spider): """ process_response函數(shù):攔截所有的響應(yīng) :param request: 響應(yīng)對應(yīng)的請求 :param response: 攔截到的響應(yīng) :param spider: 爬蟲類實例化的對象 :return: 返回處理后的響應(yīng) """ return response def process_exception(self, request, exception, spider): """ 攔截發(fā)生異常的請求;對異常請求進行修正,讓其變成正常請求 :param request: 攔截到的發(fā)生異常的請求對象 :param exception: 攔截到的異常信息 :param spider: 爬蟲類實例化的對象 :return: 將修正后的請求對象進行重新發(fā)送 """ # 代理操作 if request.url.split(":")[0] == 'http': request.meta['proxy'] = "http:{}".format(random.choice(PROXY_HTTP)) else: request.meta['proxy'] = "https:{}".format(random.choice(PROXY_HTTPS)) return request # 將修正后的請求對象進行重新發(fā)送
實現(xiàn)流程:
1,解析出5個板塊對應(yīng)的url
2,對5個板塊的url發(fā)起請求
3,獲取板塊的頁面源碼數(shù)據(jù)
? 問題:數(shù)據(jù)為動態(tài)加載,源碼數(shù)據(jù)中沒有新聞標(biāo)題和詳情頁的url
? 解決:將響應(yīng)數(shù)據(jù)進行篡改,改成包含動態(tài)加載的數(shù)據(jù)
selenium幫助我們捕獲到包含了動態(tài)加載的響應(yīng)數(shù)據(jù)
selenium在scrapy中的使用
在settings.py基本設(shè)置一下,打開下載中間件
spiderNmae.py
# -*- coding: utf-8 -*-import scrapyfrom selenium import webdriverfrom wangyiPro.items import WangyiproItemclass WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['www.xx.com'] start_urls = ['https://news.163.com/'] # 5個板塊頁面的url model_urls = [] # 實例化瀏覽器對象 bro = webdriver.Chrome(executable_path=r'D:\Reptile\jupyter\onceagain\爬蟲\Scrap框架\chromedriver.exe') # 數(shù)據(jù)解析,解析5個板塊對應(yīng)頁面url def parse(self, response): li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') index = [3, 4, 6, 7, 8] for i in index: model_url = li_list[i].xpath('./a/@href').extract_first() self.model_urls.append(model_url) # 對板塊url發(fā)請求,捕獲每個頁面的源碼數(shù)據(jù) for url in self.model_urls: yield scrapy.Request(url=url, callback=self.parse_model) # 解析標(biāo)題和新聞詳情頁的url def parse_model(self, response): div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div') for div in div_list: title = div.xpath('./a/img/@alt').extract_first() new_detail_url = div.xpath('./a/@href').extract_first() if new_detail_url: item = WangyiproItem() item['title'] = title # 對新聞的詳情頁發(fā)請求,解析出新聞的內(nèi)容 yield scrapy.Request(url=new_detail_url, callback=self.parse_new_detail, meta={'item': item}) # 解析新聞內(nèi)容 def parse_new_detail(self, response): item = response.meta['item'] content = response.xpath('//*[@id="endText"]//text()').extract() content = ''.join(content) # print(content) item['content'] = content yield item # 整個程序結(jié)束時調(diào)用一次:父類的方法 def closed(self, spider): self.bro.quit()
middlewares.py
from time import sleepclass WangyiproDownloaderMiddleware(object): # 攔截響應(yīng),篡改指定響應(yīng)對象的響應(yīng)數(shù)據(jù) def process_response(self, request, response, spider): # 獲取5個板塊對應(yīng)的url model_urls = spider.model_urls bro = spider.bro if request.url in model_urls: # 成立之后定位到的response就是某一個板塊對應(yīng)的response # 指定響應(yīng)數(shù)據(jù)的篡改 # 參數(shù)body就是響應(yīng)數(shù)據(jù) bro.get(request.url) sleep(1) page_text = bro.page_source # 作為新的響應(yīng)數(shù)據(jù),包含動態(tài)加載數(shù)據(jù)源 return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request) else: return response
items.py
import scrapyclass WangyiproItem(scrapy.Item): title = scrapy.Field() content = scrapy.Field()
大文本數(shù)據(jù)就是量級大的二進制數(shù)據(jù),如圖片,壓縮包,音頻,視頻...
爬蟲文件中將二進制資源的url進行爬取和解析,將其存儲到item中向管道提交
在管道文件中指定對應(yīng)的管道類
父類:from scrapy.pipelines.images import ImagesPipeline
配置文件中進行如下操作
# 自動創(chuàng)建一個指定的文件夾IMAGES_STORE = './imgLib'
自定義一個關(guān)于ImagesPipeline該父類的管道類,在pipelines.py
中重寫如下三個方法
from scrapy.pipelines.images import ImagesPipelineimport scrapyclass ImgproPipeline(ImagesPipeline): # 發(fā)起請求 def get_media_requests(self, item, info): imgSrc = item['imgSrc'] # 請求傳參,將meta字典傳遞給了file_path這個方法 yield scrapy.Request(url=imgSrc, meta={'item': item}) # 定制get_media_request請求到數(shù)據(jù)持久化存儲的路徑(文件夾路徑+文件名稱) def file_path(self, request, response=None, info=None): # 通過request.meta接收請求傳參傳遞過來的meta字典 imgName = request.meta['item']['imgName'] return imgName # 如果配置文件中沒指定文件夾 # return '文件夾/%s.jpg' % (image_guid) def item_completed(self, results, item, info): return item
CrawlSpider
就是Spider的一個子類
創(chuàng)建一個基于CrawlSpider
爬蟲文件
scrapy genspider -t crawl spiderName www.xxx.com
LinkExtractor(allow=r'正則表達(dá)式')
:鏈接提取器
Rule(link,callback,follow=True)
:規(guī)則解析器
link:鏈接提取
callback:回調(diào)函數(shù),字符串反射調(diào)用函數(shù),解析數(shù)據(jù)
follow=True:將鏈接提取器 繼續(xù)作用到 鏈接提取器提取到的鏈接的對應(yīng)頁面中
作用:
1.將鏈接提取器提取到的鏈接進行請求發(fā)送(get)請求發(fā)送
2.請求到的數(shù)據(jù)根據(jù)指定的規(guī)則進行數(shù)據(jù)解析
深度爬取
手動發(fā)送請求解析數(shù)據(jù),請求傳參和LinkExtractor,Rule一起使用實現(xiàn)深度爬取
Rule(LinkExtractor(allow=r'正則表達(dá)式提取指定url'),callback='函數(shù)名',follow=True(提取全站頁碼url)/Flase(提取當(dāng)前葉頁碼url))
使用鏈接提取器提取詳情頁的url實現(xiàn)深度爬取
spiderName.py
中代碼
import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rule# 實例化了兩個item對象from sunPro.items import TitleItem, ContentItemclass SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1'] # 根據(jù)正則提取全站頁碼 link = LinkExtractor(allow=r'id=1&page=\d+') # 提取標(biāo)題對應(yīng)的詳情頁鏈接 link_detail = LinkExtractor(allow=r'politics/index\?id=\d+') rules = ( Rule(link, callback='parse_item', follow=True), Rule(link_detail, callback='parse_detail', follow=False), ) def parse_item(self, response): li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li') for li in li_list: title = li.xpath('./span[3]/a/text()').extract_first() detail_url = item = TitleItem() item['title'] = title yield item def parse_detail(self, response): content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first() item = ContentItem() item['content'] = content yield item
解決上述問題
手動發(fā)送請求解析詳情頁的內(nèi)容,請求傳參,傳給一個item
import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom sunPro.items import SunproItemclass SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1'] link = LinkExtractor(allow=r'id=1&page=\d+') rules = ( Rule(link, callback='parse_item', follow=False), ) def parse_item(self, response): li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li') for li in li_list: title = li.xpath('./span[3]/a/text()').extract_first() detail_url = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first() item = SunproItem() item['title'] = title yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item}) def parse_detail(self, response): content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first() item = response.meta['item'] item['content'] = content yield item
實際應(yīng)用中很少,一般都是面試時問道,主要是文原理。
概念:搭建一個分布式機群,共同執(zhí)行一組代碼,聯(lián)合對同一個資源的數(shù)據(jù)進行分布且聯(lián)合爬取
實現(xiàn)方式:
簡稱:scrapy + redis
全稱:Scrapy框架 + scrapy-redis組件
原生的scrapy框架無法實現(xiàn)分布式
原生scrapy的調(diào)度器和管道無法共享
scrapy-redis組件的作用
可以給原生的scrapy框架,提供可以被共享的管道和調(diào)度器
環(huán)境安裝
pip install scrapy-redis
實現(xiàn)流程
修改爬蟲文件中爬蟲類對應(yīng)的操作
導(dǎo)包:from scrapy_redis.spiders import RedisCrawlSpider
CrawlSpider 導(dǎo)入 RedisCrawlSpiderSpider 導(dǎo)入 RedisSpider
爬蟲類的父類修改成RedisCrawlSpider
將start_urls刪除,添加一個redis_key='可以被共享調(diào)度器隊列的名稱'
進行常規(guī)的請求和解析和向管道提交item操作即可
對settings.py
進行配置
ITEM_PIPELINES = { # scrapy組件中有管道,是基于redis的,所以目前只能用redis存儲 'scrapy_redis.pipelines.RedisPipeline':400,}
# 增加了一個去重容器類的配置,作用使用Redis的set集合來存儲請求的指紋數(shù)據(jù),從而實現(xiàn)請求去重的持久化DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"# 使用scrapy-redis組件自己的調(diào)度器SCHEDULER = "scraoy_redis.scheduler.Scheduler"# 配置調(diào)度器是否要持久化,也就是爬蟲結(jié)束,是否清空Redis中請求對列和去重的set,true表示持久化存儲,不清空SCHEDULER_PERSIST = True
REDIS_HOST = 'redis服務(wù)的ip地址'REDIS_PORT = 6379
對redis配置文件進行修改
啟動Redis服務(wù)和客戶端
攜帶配置文件啟動redis,在redis安裝目錄下運行cmd
redis-server.exe redis.windows.conf
啟動客戶端
redis-cli
啟動程序
在終端中進入到爬蟲文件對應(yīng)的目錄中
scrapy runspider spiderName.py
向調(diào)度器的隊列中扔入一個起始的url
redisl-cli
lpush redis_key的屬性值(被共享的調(diào)度器隊列名稱) 起始的網(wǎng)址
# -*- coding: utf-8 -*-import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom scrapy_redis.spiders import RedisCrawlSpiderfrom fbsPro.items import FbsproItemclass FbsSpider(RedisCrawlSpider): name = 'fbs' # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] redis_key = 'sunQueue' # 可被共享的調(diào)度器隊列的名稱 rules = ( Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True), ) def parse_item(self, response): li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li') for li in li_list: title = li.xpath('./span[3]/a/text()').extract_first() item = FbsproItem() item['title'] = title yield item
監(jiān)測網(wǎng)站數(shù)據(jù)更新情況,以便于爬取到最新更新的網(wǎng)站
核心:去重
記錄儀:
特性:永久性存儲(redis中的set)
爬取過的數(shù)據(jù)對應(yīng)的url
可以以明文的形式存儲(url數(shù)據(jù)長度較短)
記錄的數(shù)據(jù)對其生成一個數(shù)據(jù)指紋
(url數(shù)據(jù)長度比較長)
數(shù)據(jù)指紋就是該組數(shù)據(jù)的唯一標(biāo)識
# -*- coding: utf-8 -*-import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom redis import Redisfrom zlsPro.items import ZlsproItemclass ZlsSpider(CrawlSpider): name = 'zls' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1'] # 創(chuàng)建redis的鏈接對象 conn = Redis(host="127.0.0.1", port=6379, password='redis的密碼,沒有就不寫') rules = ( Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=False), ) def parse_item(self, response): # 解析出標(biāo)題和詳情頁的url(詳情頁的url需要存儲到記錄表中) li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li') for li in li_list: title = li.xpath('./span[3]/a/text()').extract_first() detail_url = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first() item = ZlsproItem() item['title'] = title # 將進行請求發(fā)送的詳情頁的url去記錄表中進行查看 ex = self.conn.sadd('urls', detail_url) if ex == 1: print('數(shù)據(jù)已更新,可爬取') yield scrapy.Request(url=detail_url, callback=self.parse_deatil, meta={'item': item}) else: print('數(shù)據(jù)未更新,不可爬') def parse_deatil(self, response): item = response.meta['item'] content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first() item['content'] = content yield item
聯(lián)系客服