
Scrapy
一 介紹
Scrapy簡介
1.Scrapy是用純Python實現一個為了爬取網站數據、提取結構性數據而編寫的應用框架,用途非常廣泛2.框架的力量,用戶只需要定制開發幾個模塊就可以輕松的實現一個爬蟲,用來抓取網頁內容以及各種圖片,非常之方便
Scrapy主要包括了以下組件:1.引擎(Scrapy)用來處理整個系統的數據流處理, 觸發事務(框架核心)2.調度器(Scheduler)用來接受引擎發過來的請求, 壓入隊列中, 并在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址3.下載器(Downloader)用于下載網頁內容, 并將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的)4.爬蟲(Spiders)爬蟲是主要干活的, 用于從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面5.項目管道(Pipeline)負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,并經過幾個特定的次序處理數據。6.下載器中間件(Downloader Middlewares)位于Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。7.爬蟲中間件(Spider Middlewares)介于Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。8.調度中間件(Scheduler Middewares)介于Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。
Scrapy運行流程
1.引擎從調度器中取出一個鏈接(URL)用于接下來的抓取,包括過濾器和對列,過濾后的url交給對列
2.引擎把URL封裝成一個請求(Request)傳給下載器
3.下載器把資源下載下來,并封裝成應答包(Response)
4.爬蟲解析Response
5.解析出實體(Item),則交給引擎,在提交到管道進行進一步的處理(持久化存儲處理)
6.解析出的是鏈接(URL),則把URL交給調度器等待抓取
都會經過引擎進行調度
二 安裝
#Windows平臺1、pip3 install wheel #安裝后,便支持通過wheel文件安裝軟件,wheel文件官網:https://www.lfd.uci.edu/~gohlke/pythonlibs3、pip3 install lxml4、pip3 install pyopenssl5、下載并安裝pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/6、下載twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted7、執行pip3 install 下載目錄\Twisted-17.9.0-cp36-cp36m-win_amd64.whl8、pip3 install scrapy#Linux平臺1、pip3 install scrapy
三 命令行工具
介紹
#1 查看幫助scrapy -hscrapy <command> -h#2 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不需要Global commands: #全局命令startproject #創建項目genspider #創建爬蟲程序 # 示例 scrapy genspider baidu www.baidu.com settings #如果是在項目目錄下,則得到的是該項目的配置runspider #運行一個獨立的python文件,不必創建項目shell #scrapy shell url地址 在交互式調試,如選擇器規則正確與否fetch #獨立于程單純地爬取一個頁面,可以拿到請求頭view #下載完畢后直接彈出瀏覽器,以此可以分辨出哪些數據是ajax請求version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本Project-only commands: #項目文件下crawl #運行爬蟲,必須創建項目才行,確保配置文件中ROBOTSTXT_OBEY = Falsecheck #檢測項目中有無語法錯誤list #列出項目中所包含的爬蟲名edit #編輯器,一般不用parse #scrapy parse url地址 --callback 回調函數 #以此可以驗證我們的回調函數是否正確bench #scrapy bentch壓力測試#3 官網鏈接https://docs.scrapy.org/en/latest/topics/commands.html
示例
#1、執行全局命令:請確保不在某個項目的目錄下,排除受該項目配置的影響
scrapy startproject MyProject(項目名)cd MyProject (切換到項目下)
scrapy genspider baidu www.baidu.com #創建爬蟲程序#(baidu)爬蟲名 對應相關域名(www.baidu.com可以先隨便寫個www.xxx.com ) #代表這個爬蟲程序只能爬取www.baidu.com 這個域名或者百度的子域名scrapy settings --get XXX #如果切換到項目目錄下,看到的則是該項目的配置scrapy runspider baidu.py #執行爬蟲程序scrapy shell https://www.baidu.comresponseresponse.statusresponse.bodyview(response)scrapy view https://www.taobao.com #如果頁面顯示內容不全,不全的內容則是ajax請求實現的,以此快速定位問題scrapy fetch --nolog --headers https://www.taobao.comscrapy version #scrapy的版本scrapy version -v #依賴庫的版本#2、執行項目命令:切到項目目錄下
scrapy crawl baidu
scrapy check
scrapy list
scrapy parse http://quotes.toscrape.com/ --callback parse
scrapy bench
四 項目結構以及爬蟲應用簡介
目錄結構
project_name/scrapy.cfgproject_name/__init__.pyitems.pypipelines.pysettings.pyspiders/__init__.py爬蟲1.py爬蟲2.py爬蟲3.py
應用說明
scrapy.cfg:爬蟲項目的配置文件。__init__.py:爬蟲項目的初始化文件,用來對項目做初始化工作。items.py:爬蟲項目的數據容器文件,用來定義要獲取的數據。pipelines.py:爬蟲項目的管道文件,用來對items中的數據進行進一步的加工處理。settings.py:爬蟲項目的設置文件,包含了爬蟲項目的設置信息。middlewares.py:爬蟲項目的中間件文件,
pycharm中運行爬蟲程序
在項目目錄先創建entrypoint.py文件,文件名不能變
from scrapy.cmdline import execute
execute(['scrapy','crawl','baidu','--nolog']) #百度為爬蟲名,列表的前兩項不變 --nolog可寫可不寫,作用是不在打印其他配置項,只打印需要的內容
五 Spiders
1.介紹
1.Spider是由一系列類(定義了一個網址一組網址將別爬取)組成,具體包括了如何執行爬取任務并且如何從頁面中提取結構化的數據
2.Spider是你為了一個特定的網址或一組網址自定義爬取或解析頁面行為的地方
2.Spider會循環做的事情
#1、生成初始的Requests來爬取第一個URLS,并且標識一個回調函數
第一個請求定義在start_requests()方法內默認從start_urls列表中獲得url地址來生成Request請求,默認的回調函數是parse方法。回調函數在下載完成返回response時自動觸發#2、在回調函數中,解析response并且返回值
返回值可以4種:包含解析數據的字典Item對象新的Request對象(新的Requests也需要指定一個回調函數)或者是可迭代對象(包含Items或Request)#3、在回調函數中解析頁面內容
通常使用Scrapy自帶的Selectors,但很明顯你也可以使用Beutifulsoup,lxml或其他你愛用啥用啥。#4、最后,針對返回的Items對象將會被持久化到數據庫
通過Item Pipeline組件存到數據庫:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
或者導出到不同的文件(通過Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)
3.爬取格式
entrypoint.py
from scrapy.cmdline import execute
execute(['scrapy','crawl','amazon1','-a','keywords=iphone8手機','--nolog'])
加參數的格式
# -*- coding: utf-8 -*-
import scrapy
from urllib.parse import urlencodeclass Amazon1Spider(scrapy.Spider):name = 'amazon1'allowed_domains = ['www.amazon.cn'] #也可以不用,寫的話就會對請求的url進行限制,下邊的start_urls就只能請求allowed_domains中urlstart_urls = ['https://www.amazon.cn/'] #可以放多個請求的url#不寫allowed_domains,就可以在start_urls請求列表中寫多個url# 自定義的配置,可以加請求頭系列的配置,先從這里找,沒有去settings中找custom_settings = {'REQUEST_HEADERS':{'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36',}}# 規定外部傳進來的參數def __init__(self,keywords,*args,**kwargs):super(Amazon1Spider,self).__init__(*args,**kwargs)self.keywords=keywords# 發送請求def start_requests(self):url = 'https://www.amazon.cn/s?%s&ref=nb_sb_noss_1' %(urlencode({'k':self.keywords}))yield scrapy.Request(url=url,callback=self.parse,dont_filter=True) #請求方式GET#post請求 : scrapy.FormRequest(url,formdata=data,callback)# 解析def parse(self, response):detail_url = response.xpath('//*[@id="search"]/div[1]/div[2]/div/span[4]/div[1]/div[1]/div/span/div/div/div[2]/div[3]/div/div/h2/a/@href') #如果這里得到多個url,商品詳情url求,通過for循環再次對詳情的url返送請求,每次返送請求必須有對應的回調函數for url in detail_url: yiled yield scrapy.Request(url=url,callback=self.parse_detaildont_filter=True)print(detail_url)def parse_detail(self,response):print(response) #詳情頁的響應結果def close(spider, reason): #解析之后執行closeprint('結束')
4.示例
爬取三國演義的文章標題以及每篇文章的內容
from scrapy.cmdline import execute
execute(['scrapy','crawl','sang','--nolog'])
# -*- coding: utf-8 -*-
import scrapy
class SangSpider(scrapy.Spider):name = 'sang'allowed_domains = ['www.shicimingju.com/book/sanguoyanyi.html']start_urls = ['http://www.shicimingju.com/book/sanguoyanyi.html/']custom_settings = { #加一些請求頭信息'REQUEST_HEADERS':{'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36',}}def start_requests(self):url = 'http://www.shicimingju.com/book/sanguoyanyi.html/'yield scrapy.Request(url=url,callback=self.parse,#解析主頁面的回調函數) #向主頁面發起請求def parse(self, response): #解析主頁面的響應信息all_urls = response.xpath('/html/body/div[4]/div[2]/div[1]/div/div[4]/ul/li/a/@href').extract()#所有的文章的url all_title = response.xpath('/html/body/div[4]/div[2]/div[1]/div/div[4]/ul/li/a/text()').extract()#所有標題for url in all_urls:# print(response.urljoin(url)) #拼接url detail_urls = 'http://www.shicimingju.com'+urlprint(detail_urls)yield scrapy.Request(url=detail_urls, callback=self.parse_detail,#解析詳情頁面的回調函數dont_filter=True) #向詳細文章發送請求def parse_detail(self,response):content = response.xpath('/html/body/div[4]/div[2]/div[1]/div[1]/div/p/text()') #解析詳情文章的響應print(content)
5.數據解析
- scrapy中封裝的xpath的方式進行數據解析。
- scrapy中的xpath和etree中的xpath的區別是什么?etree中的xpath返回的是字符串或列表scrapy的xpath進行數據解析后返回的列表元素為Selector對象,然后必須通過extract或者extract_first這兩個方法將Selector對象中的對應數據取出extract_first:將列表元素中第0個selector對象提取
extract:取出列表中的每一個selector對象提取
6.數據持久化存儲
基于終端的指令存儲
特性:只能將parse方法的返回值存儲到本地的磁盤文件中
指令:scrapy crawl 爬蟲程序名 -o 文件名
局限性:只能存儲到磁盤中,存儲的文件名有限制,只能用提供的文件名
示例
# 爬取糗事百科笑話的標題和內容
import scrapy
class QiubSpider(scrapy.Spider):name = 'qiub'start_urls = ['http://www.lovehhy.net/Joke/Detail/QSBK/']def parse(self, response):all_data = []#response.xpath拿到的是個列表里面是Selector對象title = response.xpath('//*[@id="footzoon"]/h3/a/text()').extract()content = response.xpath('//*[@id="endtext"]//text()').extract() #因為在內容中有對個br標簽進行分割,所以用//text()title=''.join(title)content=''.join(content)dic = {'title':title,'content':content}all_data.append(dic)return all_data #必須有返回值才能用基于終端指令的存儲
終端存儲指令:scrapy crawl qiub -o qiubai.csv(csv是提供的文件類型)
基于管道存儲
實現流程
- 基于管道:實現流程1.數據解析2.在item類中定義相關的屬性3.將解析的數據存儲或者封裝到一個item類型的對象(items文件中對應類的對象)4.向管道提交item5.在管道文件的process_item方法中接收item進行持久化存儲6.在配置文件中開啟管道管道只能處理item類型的對象
文件形式
示例
qiub.py
# -*- coding: utf-8 -*-
import scrapy
from qiubai.items import QiubaiItemclass QiubSpider(scrapy.Spider):name = 'qiub'start_urls = ['http://www.lovehhy.net/Joke/Detail/QSBK/']def parse(self, response):all_data = []title = response.xpath('//*[@id="footzoon"]/h3/a/text()').extract()content = response.xpath('//*[@id="endtext"]//text()').extract()title=''.join(title)content=''.join(content)dic = {'title':title,'content':content}all_data.append(dic)item = QiubaiItem() #實例化一個item對象item['title'] = title #封裝好的數據結構item['content'] = contentyield item #向管道提交item,提交給優先級最高的管道類
items.py
import scrapy
class QiubaiItem(scrapy.Item):# define the fields for your item here like:title = scrapy.Field() #Fieid可以接受任意類型的數據格式/類型content = scrapy.Field()
pipelines.py
class QiubaiPipeline(object):f= Noneprint('開始爬蟲.....')def open_spider(self,spider): # 重寫父類方法,開啟文件self.f = open('qiubai.txt','w',encoding='utf-8')def close_spider(self,spider):# 重寫父類方法,關閉文件print('結束爬蟲')self.f.close()def process_item(self, item, spider):#item是管道提交過來的item對象title = item['title'] #取值content = item['content']self.f.write(title+':'+content+'\n') #寫入文件return item
settings.py
# 開啟管道,可以開啟多個管道
ITEM_PIPELINES = {'qiubai.pipelines.QiubaiPipeline': 300, #300表示的優先級
}
將同一份數據持久化到不同的平臺
1.管道文件中的一個管道類負責數據的一種形式的持久化存儲
2.爬蟲文件向管道提交的item只會提交給優先級最高的那一個管道類
3.在管道類的process_item中的return item表示的是將當前管道接收的item返回/提交給下一個即將被執行的管道類
數據庫(mysql)
示例
import pymysql
# 負責將數據存儲到mysql
class MysqlPL(object):conn = Nonecursor = Nonedef open_spider(self, spider):self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123', db='spider',charset='utf8')print(self.conn)def process_item(self, item, spider):author = item['author']content = item['content']sql = 'insert into qiubai values ("%s","%s")' % (author, content)self.cursor = self.conn.cursor()try:self.cursor.execute(sql) #執行正確的話就提交事務self.conn.commit()except Exception as e:print(e)self.conn.rollback() #回滾,如果出現錯誤就回滾return itemdef close_spider(self, spider):self.cursor.close()self.conn.close()
7.爬取校花網圖片
ImagesPipeline的簡介
專門爬取圖片的管道1.爬取一個Item,將圖片的URLs放入image_urls字段
2.從Spider返回的Item,傳遞到Item Pipeline
3.當Item傳遞到ImagePipeline,將調用Scrapy 調度器和下載器完成4.image_urls中的url的調度和下載。
5.圖片下載成功結束后,圖片下載路徑、url和校驗和等信息會被填充到images字段中。
ImagesPipeline的使用:
from scrapy.pipelines.images import ImagesPipeline
import scrapy
# 通過重寫父類方法
class SpiderImgPipeline(ImagesPipeline):# 對某一個媒體資源進行請求發送# item是提交過來的item(src)def get_media_requests(self, item, info): yield scrapy.Request(item['src'])# 制定媒體數據存儲的名稱def file_path(self, request, response=None, info=None):img_name = request.url.split('/')[-1]print(img_name+'正在爬取')return img_name# 將item傳遞個下一個即將被執行的管道類def item_completed(self, results, item, info):return item# 在配置中添加圖片的存儲路徑
IMAGES_STORE = './imgslib'
爬取示例:
settings.py
IMAGES_STORE = './imgslib' #存儲爬取到的圖片
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
ITEM_PIPELINES = {'spider_img.pipelines.SpiderImgPipeline': 300,
}
img.py
# -*- coding: utf-8 -*-
import scrapy
from spider_img.items import SpiderImgItemclass ImgSpider(scrapy.Spider):name = 'img'start_urls = ['http://www.521609.com/daxuexiaohua/']url= 'http://www.521609.com/daxuexiaohua/list3%d.html' #通用的url模板,需要加page頁數,每個頁數的模板page_num = 1def parse(self, response):li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')for li in li_list:img_src = 'http://www.521609.com/' + li.xpath('./a[1]/img/@src').extract_first() #獲取的所有的圖片的url,extract_first獲取的字符串類型,extract獲取的是list,里面存了selector對象item = SpiderImgItem() #實例化item對象item['src'] = img_src #把數據添加到item中yield item #提交到itemif self.page_num < 3: #爬取的前兩頁self.page_num += 1new_url = format(self.url%self.page_num) #其他頁數的urlyield scrapy.Request(new_url,callback=self.parse) #對頁數發請求
items.py
import scrapy
class SpiderImgItem(scrapy.Item):src = scrapy.Field()pass
piplines.py
from scrapy.pipelines.images import ImagesPipeline
import scrapyclass SpiderImgPipeline(ImagesPipeline):# 對某一個媒體資源進行請求發送# item是提交過來的item(src)def get_media_requests(self, item, info):yield scrapy.Request(item['src'])# 制定媒體數據存儲的名稱def file_path(self, request, response=None, info=None):img_name = request.url.split('/')[-1]print(img_name+'正在爬取')return img_name# 將item傳遞個下一個即將被執行的管道類def item_completed(self, results, item, info):return item
8.請求傳參(深度爬取)
示例:爬取4567tv電影網的電影信息
movie.py
# 需求:爬取電影網的電影名稱和電影的簡介
# 分析:去電影網的首頁請求會看到所有的電影,但不能獲取簡介,需要現獲取所有的電影的url,在對每個電影的url反請求,對電影詳情發請求獲取簡介import scrapy
from movie_spider.items import MovieSpiderItemclass MovieSpider(scrapy.Spider):name = 'movie'start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']page_num = 1def parse(self, response):movie_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')for movie_li in movie_list:movie_title = movie_li.xpath('./div[1]/a/@title').extract_first()movie_url = 'https://www.4567tv.tv'+movie_li.xpath('./div[1]/a/@href').extract_first()item = MovieSpiderItem()item['movie_title'] = movie_titleyield scrapy.Request(movie_url,callback=self.parse_movie_detail,meta={'item':item})#請求傳參if self.page_num < 5:self.page_num += 1new_url = f'https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1/page/{self.page_num}.html'yield scrapy.Request(url=new_url,callback=self.parse,meta={'item':item})def parse_movie_detail(self,response):item = response.meta['item']#取出item,接收movie_about = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()item['movie_about'] = movie_about #電影的簡介yield item# 深度爬取(傳參):在電影首頁獲取不到電影的簡介,在最后提交item時,電影的標題和電影的簡介在不同的解析回調函數中,所以先把標題放在item中(但是不提交),傳遞給下一個回調函數,繼續用item,向item中存入要提交的數據,!!!# 為什么不能設置為全局?# 因為在最后提交yield item時會產生數據覆蓋現象item['movie_title'] = movie_title #存
meta={'item':item} # meta參數傳遞
item = response.meta['item'] #取
items.py
import scrapyclass MovieSpiderItem(scrapy.Item):# define the fields for your item here like:movie_title = scrapy.Field()movie_about = scrapy.Field()pass
pipelines.py
import pymysql
class MovieSpiderPipeline(object):conn = Nonecursor = Nonedef open_spider(self, spider):self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123', db='movie_spider',charset='utf8')def process_item(self, item, spider):movie_title = item['movie_title']movie_about = item['movie_about']sql = 'insert into movie_info values ("%s","%s")' % (movie_title, movie_about)self.cursor = self.conn.cursor()try:self.cursor.execute(sql) # 執行正確的話就提交事務self.conn.commit()except Exception as e:print(e)self.conn.rollback() # 回滾,如果出現錯誤就回滾return itemdef close_spider(self, spider):self.cursor.close()self.conn.close()
9.scrapy中間件的應用
下載中間件
-
作用:批量攔截請求和響應
-
攔截請求:
? 1.UA偽裝
? 2.代理操作
-
攔截響應:
? 1.篡改響應數據,篡改不滿足需求的響應對象,例如有動態加載的數據,直接請求是請求不到的
? 2.直接更換響應對象
- process_request request通過下載中間件時,該方法被調用
- process_response 下載結果經過中間件時被此方法處理
- process_exception 下載過程中出現異常時被調用# process_request(request, spider) :當每個request通過下載中間件的時候, 該方法被調用, 該方法必須返回以下三種中的任意一種:1. None: Scrapy將繼續處理該request, 執行其他的中間件的響應方法, 知道何時的下載器處理函數(download handler)被調用, 該request被執行(其resposne被下載)2. Response對象: Scrapy將不會調用人其他的process_request()或者process_exception()方法, 或者響應的下載函數; 其將返回response. 已安裝的中間件的process_response()方法胡子愛每個res[onse返回時被調用3. Request對象或raise異常: 返回Request對象時: Scrapy停止調用process_request方法并重新調度返回的request. 當新的request被執行之后, 相應的中間件將會根據下載的response被調用.當raises異常時: 安裝的下載中間件的process_exception()方法會被調用. 如果沒有任何一個方法處理該異常, 則request的errback(Request.errback)方法被調用. 如果沒有代碼處理拋出的異常, 則該異常被忽略且不被記錄.# process_response(request, response, spider) :process_response的返回值也是有三種:1. response對象: 如果返回的是一個Resopnse(可以與傳入的response相同, 也可以是全新的對象), 該response會被鏈中的其他中間件的process_response()方法處理.2. Request對象: 如果其返回一個Request對象, 則中間件鏈停止, 返回的request會被重新調度下載. 處理類似于process_request()返回request所做的那樣.3. raiseu異常: 如果其拋出一個lgnoreRequest異常, 則調用request的errback(Request.errback). 如果沒有代碼處理拋出的異常, 則該異常被忽略且不記錄.process_exception(request, exception, spider) :當下載處理器(downloader handler)或者process_request()(下載中間件)拋出異常(包括lgnoreRequest異常)時, Scrapy調用process_exception().# process_exception()也是返回三者中的一個:1. 返回None: Scrapy將會繼續處理該異常, 接著調用已安裝的其他中間件的process_exception()方法,知道所有的中間件都被調用完畢, 則調用默認的異常處理.2. 返回Response: 已安裝的中間件鏈的process_response()方法被調用. Scrapy將不會調用任何其他中間件的process_exception()方法.3. 返回一個Request對象: 返回的額request將會被重新調用下載. 浙江停止中間件的process_exception()方法的執行, 就如返回一個response那樣. 相當于如果失敗了可以在這里進行一次失敗的重試, 例如當訪問一個網站出現因為頻繁爬取被封ip就可以在這里設置增加代理繼續訪問.
爬蟲中間件
案例
爬蟲程序
# -*- coding: utf-8 -*-
import scrapy
import requests
from wangyi.items import WangyiItem
from selenium import webdriver
class WySpider(scrapy.Spider):name = 'wy'start_urls = ['https://news.163.com/']un_url = []bro = webdriver.Chrome(executable_path=r'C:\pycahrm文件\chromedriver.exe')def parse(self, response):li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')model_indexs = [3, 4, 6, 7, 8]for index in model_indexs:li_tag = li_list[index]# 解析出了每一個板塊對應的urlmodel_url = li_tag.xpath('./a/@href').extract_first()self.un_url.append(model_url) # 里面的內容是動態生成的,所以是不滿足條件的url,需要在中間件中進行進一步處理yield scrapy.Request(model_url,callback=self.news_parse)def news_parse(self, response):div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')for div in div_list:title = div.xpath('./div/div[1]/h3/a/text()').extract_first()# title = div.xpath('./div/div/div[1]/h3/a/text()').extract_first()news_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()item = WangyiItem()#對得到結果進行去除空格換行item['title'] = title.replace('\n','')item['title'] = title.replace('\t','')item['title'] = title.replace(' ','')item['title'] = title.replace('\t\n','')yield scrapy.Request(news_url,callback=self.detail_parse,meta={'item':item})def detail_parse(self,response):item = response.meta['item']content = response.xpath('//*[@id="endText"]//text()').extract()content = ''.join(content)#對得到結果進行去除空格換行item['content'] = content.replace('\n','')item['content'] = content.replace('\t','')item['content'] = content.replace(' ','')item['content'] = content.replace('\t\n','')item['content'] = content.replace(' \n','')yield item
下載中間件
from scrapy import signals
from scrapy.http import HtmlResponse
import timeclass WangyiDownloaderMiddleware(object):def process_request(self, request, spider):return Nonedef process_response(self, request, response, spider):if request.url in spider.un_url:spider.bro.get(request.url)time.sleep(3)spider.bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')time.sleep(2)spider.bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')time.sleep(2)page_text = spider.bro.page_source# 新的響應的構造方法new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)return new_response #攔截響應后并篡改后,并重新發送新的響應對象,響應的構造方法else:return responsedef process_exception(self, request, exception, spider):pass
10.CrawlSpider(全站數據爬取)
創建一個基于CrawlSpider的爬蟲文件:scrapy genspider -t crawl sun www.xxx.com
CrawlSpider使用
爬取陽光熱線問政平臺所有頁碼中的內容標題以及狀態http://wz.sun0769.com/html/top/report.shtml
陽光問政平臺共151711條記錄,好幾百頁,實現這幾百頁的數據爬取
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor #鏈接提取器
from scrapy.spiders import CrawlSpider, Rule# rule規則解析器
# 鏈接提取器:提取鏈接,可以根據指定的規則進行指定的連鏈接的提取
# 提取規則:allow='正則表達式'# 規則解析器:獲取鏈接提取到的鏈接,然后對其進行請求發送,根據指定的規則(callback)對請求的頁面源碼數據進行數據解析class SunSpider(CrawlSpider):name = 'sun'start_urls = ['http://wz.sun0769.com/index.php/question/report?page=']link = LinkExtractor(allow=r'page=\d+')rules = (# 實例化一個rule對象,基于linkextractorRule(link, callback='parse_item', follow=True),) # fllow=true 將鏈接提取器繼續作用到鏈接提取器提取的頁碼鏈接所對應 的頁面中.如果fllow=Flase,得到的頁面數據只是當前頁面的顯示的頁碼數據def parse_item(self, response):tr_list = response.xpath('/html//div[8]/table[2]//tr')for tr in tr_list:title = tr.xpath('./td[3]/a[1]/@title').extract_first()status = tr.xpath('./td[4]/span/text()').extract_first()print(title,status)
基于CrawlSpider的深度爬取:爬取所有頁碼中的詳情數據
sun.py
import scrapy
from scrapy.linkextractors import LinkExtractor #鏈接提取器
from scrapy.spiders import CrawlSpider, Rule# rule規則解析器
from sun_spider.items import SunSpiderItem,SunSpiderItem_secondclass SunSpider(CrawlSpider):name = 'sun'start_urls = ['http://wz.sun0769.com/index.php/question/report?page=']# href="http://wz.sun0769.com/html/question/201912/437515.shtml"link = LinkExtractor(allow=r'page=\d+')link_detail = LinkExtractor(allow=r'question/\d+/\d+/.shtml')rules = (# 實例化一個rule對象,基于linkextractorRule(link, callback='parse_item', follow=True),Rule(link_detail, callback='parse_detail'),) # fllow=true 將鏈接提取器繼續作用到鏈接提取器提取的頁碼鏈接所對應 的頁面中def parse_item(self, response):tr_list = response.xpath('/html//div[8]/table[2]//tr')for tr in tr_list:title = tr.xpath('./td[3]/a[1]/@title').extract_first()status = tr.xpath('./td[4]/span/text()').extract_first()num = tr.xpath('./td[1]/text()').extract_first()item = SunSpiderItem_second()item['title'] = titleitem['status'] = statusitem['num'] = num #用于mysql數據庫的條件存儲,num就是存儲的條件if num:yield itemdef parse_detail(self,response):content = response.xpath('/html/body/div[9]/table[2]/tbody/tr[1]/td//text()').extract()num = response.xpath('/html/body/div[9]/table[1]/tbody/tr/td[2]/span[2]/text()').extract_first()num = num.split(':')[-1]if num:content = ''.join(content)item = SunSpiderItem()item['content'] = contentitem['num'] = numyield item
items.py
import scrapyclass SunSpiderItem(scrapy.Item):# define the fields for your item here like:content = scrapy.Field()num = scrapy.Field()
class SunSpiderItem_second(scrapy.Item):# define the fields for your item here like:title = scrapy.Field()status = scrapy.Field()num = scrapy.Field()
pipelines.py
# -*- coding: utf-8 -*-# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.htmlimport pymysql
class SunSpiderPipeline(object):conn = Nonecursor = Nonedef 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):if item.__class__.__name__ == 'SunSpiderItem':content = item['content']num = item['num']sql = f'insert into spider_crawl_sun values {(content)} where num={num}'print(num)# sql = 'insert into spider_crawl_sun values ("%s") where num=%s'%(content,num)self.cursor = self.conn.cursor()try:self.cursor.execute(sql) # 執行正確的話就提交事務self.conn.commit()except Exception as e:self.conn.rollback()return itemelif item.__class__.__name__=='SunSpiderItem_second':title = item['title']status = item['status']num = item['num']sql = 'insert into spider_crawl_sun values ("%s","%s","%s")'%(title,status,num)print(sql)self.cursor = self.conn.cursor()try:self.cursor.execute(sql) # 執行正確的話就提交事務self.conn.commit()except Exception as e:self.conn.rollback()return itemdef close_spider(self, spider):self.cursor.close()self.conn.close()
11.提升scrapy爬取數據的效率
需要將如下五個步驟配置在配置文件中即可
增加并發:默認scrapy開啟的并發線程為32個,可以適當進行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值為100,并發設置成了為100。降低日志級別:在運行scrapy時,會有大量日志信息的輸出,為了減少CPU的使用率。可以設置log輸出信息為INFO或者ERROR即可。在配置文件中編寫:LOG_LEVEL = ‘INFO’禁止cookie:如果不是真的需要cookie,則在scrapy爬取數據時可以禁止cookie從而減少CPU的使用率,提升爬取效率。在配置文件中編寫:COOKIES_ENABLED = False禁止重試:對失敗的HTTP進行重新請求(重試)會減慢爬取速度,因此可以禁止重試。在配置文件中編寫:RETRY_ENABLED = False減少下載超時:如果對一個非常慢的鏈接進行爬取,減少下載超時可以能讓卡住的鏈接快速被放棄,從而提升效率。在配置文件中進行編寫:DOWNLOAD_TIMEOUT = 10 超時時間為10s
本文僅做項目練習,切勿商用!
由于文章篇幅有限,文檔資料內容較多,需要這些文檔的朋友,可以加小助手微信免費獲取,【保證100%免費】,中國人不騙中國人。
今天就分享到這里,感謝大家收看。