Python爬蟲之scrapy框架基礎理解
1 scrapy
1.1 簡介
scrapy
框架Python
編寫 ,是 一個快速、高層次的螢幕抓取和 web 抓取框架,用於抓取 web
站點並從頁面中提取結構化的資料。
Scrapy
用途廣泛,可以用於資料探勘、監測和自動化測試,還有高效能的持久化儲存,非同步的資料下載,高效能的資料解析,分散式等等
1.1.1 scrapy原理
scrapy有五大核心元件:
引擎
:用來處理整個系統的資料流處理,觸發事務(框架核心)排程器
:用來接受引擎發過來的請求,壓入佇列中,並在引擎再次請求的時候返回,可以想象成一個URL
的優先佇列,由它來決定下一個喜歡的網址是什麼,同時去除重複的網址下載器
:用於下載網頁內容,並將網頁內容返回,scrapy下載是建立在twisted
這個高效的非同步模型上的爬蟲
:爬蟲主要幹活的,用於從特定網頁中提取自己想要的資訊,使用者也可以從中爬取出連結,讓Scrapy爬取下一個頁面管道
:負責處理頁面中提取的實體,主要功能是持久化實體,驗證實體的有效性,清除不需要資訊,當頁面被爬蟲解析後,將解析傳送到專案管道,並經過幾個特定次序處理資料
1.2 環境安裝
mac
或者linux
系統直接:pip install scrapy
windows
系統,會比較麻煩:
-
首先安裝
wheel
:pip install wheel
-
下載
twisted
:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下載對應的whl
檔案。如下圖,cp
後面是python
版本,amd64
代表64
位,劃線的是我要下載的,點選下載
-
在下載後的
twisted
資料夾內執行pip
命令來安裝:pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
如果安裝報錯:
ERROR: Exception:
Traceback (most recent call last):
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\base_command.py", line 173, in _main
status = self.run(options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\req_command.py", line 203, in wrapper
return func(self, options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\commands\install.py", line 315, in run
requirement_set = resolver.resolve(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\resolver.py", line 75, in resolve
collected = self.factory.collect_root_requirements(root_reqs)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 471, in collect_root_requirements
req = self._make_requirement_from_install_req(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 433, in _make_requirement_from_install_req
cand = self._make_candidate_from_link(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 204, in _make_candidate_from_link
self._link_candidate_cache[link] = LinkCandidate(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 295, in init
super().init(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 156, in init
self.dist = self._prepare()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 227, in _prepare
dist = self._prepare_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 305, in _prepare_distribution
return self._factory.preparer.prepare_linked_requirement(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 508, in prepare_linked_requirement
return self._prepare_linked_requirement(req, parallel_builds)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 570, in _prepare_linked_requirement
dist = _get_prepared_distribution(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 61, in _get_prepared_distribution
return abstract_dist.get_pkg_resources_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\distributions\wheel.py", line 26, in get_pkg_resources_distribution
with ZipFile(self.req.local_file_path, allowZip64=True) as z:
File "zipfile.py", line 1257, in init
File "zipfile.py", line 1324, in _RealGetContents
zipfile.BadZipFile: File is not a zip file
可能是檔案受損,重新下載執行上述命令即可成功
- 安裝
pywin32
:pip install pywin32
- 安裝
scrapy
:pip install scrayp
最後測試是否安裝成功,在DOS
終端裡錄入scrapy
指令,沒有報錯即表示安裝成功
1.3 使用scrapy
1.3.1 使用步驟
- 建立工程:
scrapy startproject ProjectName
- 建立爬蟲檔案
先進入到工程目錄中,然後執行命令scrapy genspider spiderFileName www.xxx.com
,就會在spriders
子目錄中建立一個爬蟲檔案
例如執行:scrapy genspider firstSpiderFile www.xxx.com
會生成一個檔案firstSpiderFile.py 且內部有字尾為Spider
的類
allowed_domains
:允許的域名,限定start_urls
列表中哪些url
可以傳送請求,如果註釋掉,會全部發送
import scrapy
class FirstspiderfileSpider(scrapy.Spider):
#爬蟲檔名字,爬蟲原始檔一個唯一標識
name = 'firstSpiderFile'
#允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
#allowed_domains = ['www.baidu.com']
#起始的url列表:列表中的存放的url被scrapy會自動傳送請求
start_urls = ['http://www.baidu.com/']
#用於資料解析:response引數表示請求成功後對應的響應物件,如果解析返回物件有多個,那麼這個pass方法就要呼叫多次
def parse(self, response):
print(response)
- 執行工程,輸入爬蟲檔案
spiderFileName
:scrapy crawl spiderFileName
- 在爬蟲檔案中新增相關程式碼
注意:
在爬蟲檔案使用xpath
解析時,返回可能不是字串,而是Selector
物件,就可以用extract()
方法解析`
1.4 持久化儲存
1.4.1 基於終端指令
基於終端指令,只可以將parse
方法的返回值儲存到本地的文字檔案中,但是隻支援一下幾種格式:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
使用如下命令,就是在執行命令增加一個-o
引數:
scrapy crawl spiderFileName -o 檔案路徑+檔名
import scrapy
class FirstspiderfileSpider(scrapy.Spider):
#爬蟲檔名字,爬蟲原始檔一個唯一標識
name = 'firstSpiderFile'
#允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
#allowed_domains = ['www.baidu.com']
#起始的url列表:列表中的存放的url被scrapy會自動傳送請求
start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']
#用於資料解析:response引數表示請求成功後對應的響應物件,如果解析返回物件有多個,那麼這個pass方法就要呼叫多次
def parse(self, response):
div_list = response.xpath('//div[@class="shici_list_main"]')
all_data=[]
for div in div_list:
#此處使用相對路徑
shici_name=div.xpath("h3/a/text()")[0].extract()
# print(shici_name)
shici_text_list=div.xpath("div//text()").extract()
shici_text_list = "".join(shici_text_list)
dict={
'title':shici_name,
"content":shici_text_list
}
all_data.append(dict)
return all_data
注意:
在爬蟲檔案的parse
方法需要有返回值來儲存檔案
1.4.2 基於管道
1.4.2.1 基於管道步驟
基於管道大致步驟:(即把要寫的程式碼寫到items.py
檔案中)
- 在
item類
中定義相關的屬性(items.py
) - 把要解析的資料封裝儲存到
item類
型物件(items.py
) - 將
item類
的資料物件items.py
提交給管道pipelines.py
進行持久化儲存操作 - 在管道類的
process_item
中藥將其接收到item
物件中儲存的資料進行持久化儲存操作 - 在配置檔案
settings.py
中開啟管道
1.4.2.2 基於管道操作
在item類
中定義相關的屬性
import scrapy
class FirstItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title=scrapy.Field()
content=scrapy.Field()
把要解析的資料封裝儲存到item類
型物件
注意:
此處的爬蟲檔案是不需要return
但是需要yield
import scrapy
from first.items import FirstItem
class FirstspiderfileSpider(scrapy.Spider):
#爬蟲檔名字,爬蟲原始檔一個唯一標識
name = 'firstSpiderFile'
#允許的域名,限定start_urls列表中哪些url可以傳送請求,如果註釋掉,會全部發送
#allowed_domains = ['www.baidu.com']
#起始的url列表:列表中的存放的url被scrapy會自動傳送請求
start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']
#用於資料解析:response引數表示請求成功後對應的響應物件,如果解析返回物件有多個,那麼這個pass方法就要呼叫多次
def parse(self, response):
div_list = response.xpath('//div[@class="shici_list_main"]')
all_data=[]
for div in div_list:
#此處使用相對路徑
shici_name=div.xpath("h3/a/text()")[0].extract()
# print(shici_name)
shici_text_list=div.xpath("div//text()").extract()
shici_text_list = "".join(shici_text_list)
item = FirstItem()
item['title']=shici_name
item['content']=shici_text_list
yield item
pipelines.py
開始持久化操作
注意:
在process_item
中的return item
,會把item
傳遞給下一個即將被執行的管道類,不管後面有沒有管道盡量都return
from itemadapter import ItemAdapter
class FirstPipeline(object):
fp =None
#重寫父類一個方法:該方法只在開始爬蟲的時候被呼叫一次
def open_spider(self,spider):
print("開始爬蟲..............")
self.fp=open('./test123.txt','w',encoding='utf-8')
#專門用來處理item型別物件
#可以接受爬蟲檔案體積過來的item物件
#該方法每接受一個item物件就會被呼叫一次
def process_item(self, item, spider):
title=item['title']
content=item['content']
self.fp.write(title+":"+content+'\n')
return item
#繼續重寫父類方法
def close_spider(self,spider):
print('結束爬蟲列印................')
self.fp.close()
在配置檔案settings.py
中開啟管道
ITEM_PIPELINES = {
#300表示執行優先順序,數值越小,優先順序越高
'first.pipelines.FirstPipeline': 300,
}
1.4.3 多渠道儲存
如果要把爬取的資料分別存在本地和資料庫中,那麼就在pipelines.py
中寫多個管道操作類
在pipelines.py
檔案中增加如下類
class MysqlPipeline(object):
conn =None
curcos=None
#重寫父類一個方法:該方法只在開始爬蟲的時候被呼叫一次
def open_spider(self,spider):
print("mysql開始爬蟲..............")
self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='root',db='test',charset='utf-8')
#專門用來處理item型別物件
#可以接受爬蟲檔案體積過來的item物件
#該方法每接受一個item物件就會被呼叫一次
def process_item(self, item, spider):
title=item['title']
content=item['content']
self.curcos=self.conn.cursor()
self.curcos.execute('insert into test values("%s","%s")%(item["author"],item["content"]) ')
self.curcos.commit()
# 會把item傳遞給下一個即將被執行的管道類
return item
#繼續重寫父類方法
def close_spider(self,spider):
print('結束mysql爬蟲...............')
self.curcos.close()
self.conn.close()
修改settings.py
檔案
ITEM_PIPELINES = {
#300表示執行優先順序,數值越小,優先順序越高
'first.pipelines.FirstPipeline': 300,
'first.pipelines.MysqlPipeline': 301,
}
1.5 全站資料爬取
全站資料爬取就是將網站中某個板塊下的全部頁碼對應的頁碼資料進行爬取
實現方式:
- 將所有頁面url新增到start_urls列表中
- 自行手動請求,需要手動傳送請求:
yield scrapy.Request(url,callback)
其中的callback
是專門用於資料解析的
具體操作在爬蟲檔案中如下:
class XiaohuaSpider(scrapy.Spider):
name='xiaohua'
start_urls=['http://test.com/meinvxiaohua/']
url='http://test.com/meinvxiaohua/?query=python&page=%d.html'
def parse(self, response):
li_list=response.xpath('//*[@id="content"]/div[2]')
for li in li_list:
img_name=li.xpath('./a[2]/b/text()').extract_first()
if self.page_num<=3:#爬取前三頁
new_url=format(self.url%self.page_num)#替換新頁面連線
self.page_num+=1
#手動傳送請求:callback回撥函式是專門用作資料解析
yield scrapy.Request(url=new_url,callback=self.parse)#傳送新的連線,同時還是使用parse方法進行解析