Amipy
Python微型非同步爬蟲框架(A micro asynchronous Python website crawler framework)
基於Python 3.5 + 的非同步async-await 框架,搭建一個模組化的微型非同步爬蟲。可以根據需求控制非同步佇列的長度和延遲時間等。配置了可以去重的布隆過濾器,網頁內容正文過濾等,完全自主配置使用。
GitHub地址:原始碼
適用環境
- windows 7 +
- Python 3.5 +
安裝
直接使用pip安裝即可:
pip install amipy
基礎命令
- 1.檢視當前路徑下的可用命令,在DOS命令列下輸入:
>amipy
會出現命令幫助介面。
- 2.建立一個新的專案,在DOS命令列下輸入:
>amipy cproject myproject
會在當前路徑下建立一個Amipy爬蟲專案myproject。如果想要建立在指定目錄下,可以加上附加引數,-d,如:
> amipy cproject myproject -d D:somefolder
專案myproject便會在路徑D:somefolder下建立。
專案的目錄結構應該如下:
--myproject
|-spiders
| |-__init__.py
|-__init__.py
|-settings.py
其中:
settings.py 為整個專案的配置檔案,可以為整個專案下的爬蟲安裝共有的中介軟體,控制整個專案的請求併發數,設定日誌級別、檔案路徑等。
- 3.進入專案路徑,建立一個新的爬蟲,在DOS命令列下輸入:
>amipy cspider myspider
此時在專案myproject目錄下的spiders資料夾中會建立一個爬蟲目錄myspider,此時的專案結構為:
--myproject
|-spiders
| |-__init__.py
| |-myspider
| | |-__init__.py
| | |-cookies.info
| | |-item.py
| | |-settings.py
| | |-site_record.info
| | |-spider.py
| | |-url_record.info
|-__init__.py
|-settings.py
|-log.log
其中:
- 位於myspider資料夾下的settings.py為爬蟲myspider的配置檔案,該配置只對當前爬蟲有效。可以對該爬蟲的布隆過濾器進行配置,安裝中介軟體等。
- cookies.info 為爬蟲的請求cookie儲存檔案,該爬蟲爬過的所有網站的cookie會儲存進該檔案。可以通過爬蟲配置檔案settings.py進行路徑載入和儲存。
- site_record.info 為爬蟲爬取過的網站的布隆過濾器記錄檔案,方便下次爬取的時候載入,會把爬取過的網站自動去掉。防止重複爬取。
- url_record.info 為該爬蟲發出的請求url+headers+method+資料的去重後集合,爬蟲結束執行時,如果配置儲存去重url集合。下次爬取時載入該檔案可以自動過濾爬取過的所有url+headers+method+資料。
- item.py 為ORM的MongoDB資料集合物件,對應的類屬性可以對映到資料庫集合中的欄位,類名為資料表名。
- spider.py 為當前爬蟲的主要檔案,自己編寫爬取邏輯,提取規則和資料儲存指令碼等。
- 4.執行專案下的所有爬蟲,進入專案路徑,在DOS命令列下輸入:
>amipy runproject
則該專案下的所有爬蟲會開始執行,如果不想執行某個爬蟲,只需要加上引數 -e,如:
>amipy runproject -e No1spider No2spider
則名為“No1spider”、“No2spider”的爬蟲均不會執行。
- 5.執行指定的爬蟲,進入專案路徑,在DOS命令列下輸入:
>amipy runspider myspider01
則名為“myspider01”的爬蟲便會被啟動。可以加上多個爬蟲名稱,用空格隔開即可。
- 6.列出當前專案下的所有爬蟲資訊。在DOS命令列下輸入:
>amipy list
便會將當前專案下的所有爬蟲資訊列出。
使用
Amipy爬蟲編寫流程
編寫自己的爬蟲。【假設你已經安裝前面”基礎命令”建立了一個專案,並且建立了一個爬蟲名為myspider】只需要進入myspider資料夾,按照需求修改當前爬蟲的配置settings.py 以及資料儲存需要用到的表模型item.py編寫,編輯檔案spider.py,加入爬取規則邏輯等。
Url類物件
Url類物件是一個規則匹配類,它提供了許多種模式的url規則匹配。
比如:
from amipy import Url
# 表示匹配到正則模式`http://www.170mv.com/song.*`的所有連結
Url(re=`http://www.170mv.com/song.*`)
# 表示匹配到正則模式`http://www.170mv.com/song.*`的所有連結其回撥函式為`getmp3`
Url(re=`http://www.170mv.com/song/.*`,callback=`getmp3`)
# 表示匹配到地址為http協議,且路徑為‘/novel/chapter1’,引數number=2的所有連結
Url(scheme=`http`,path=`/novel/chapter1`,params=`number=2`)
# 表示匹配到域名為www.baidu.com的所有連結,為該連結請求設定代理為`127.0.0.1:1080`
Url(domain=`www.baidu.com`,proxy=`127.0.0.1:1080`)
# 表示匹配到域名為www.baidu.com的所有連結,直接扔掉這些連結。
Url(domain=`www.baidu.com`,drop=True)
Url類應用的還在於黑白名單屬性中,如在爬蟲類中的屬性:
whitelist = [
Url(re=`http://www.170mv.com/song.*`),
Url(re=`http.*.sycdn.kuwo.cn.*`),]
blacklist = [
Url(re=`http://www.170mv.com/song.*`),
Url(re=`http.*.sycdn.kuwo.cn.*`),]
表示爬蟲請求的url黑白名單匹配規則。
必要屬性
開啟spider.py ,可以看到有兩個預設的必要屬性:
- name 爬蟲的唯一標識,專案下不能有該屬性重名的爬蟲。
- urls 起始連結種子,爬蟲開始的url列表
這兩個屬性是必須的。
回撥函式
整個專案的主要實現在於回撥函式的使用,利用非同步請求得到響應後馬上呼叫其請求繫結的回撥函式來實現爬蟲的非同步爬取。
請求後響應的回撥函式(類方法)有:
- parse 返回狀態200,請求正常響應正常,可以編寫正常的規則提取、資料儲存等。
- error 狀態碼非200,出現異常狀態碼,編寫錯誤處理邏輯等。
- exception 請求出現異常,異常自定義處理。
資料儲存
Amipy目前只支援MongoDB資料庫,預設的資料庫設定在爬蟲配置檔案settings.py中。
對於爬取的資料進行儲存,預設只使用MongoDB進行資料儲存(後續可以自己擴充套件編寫ORM)。只需要開啟item.py,修改其中的示例類,原先為:
from amipy.BaseClass.orm import Model,Field
class DataItemName(Model):
...
修改其內容為:
from amipy.BaseClass.orm import Model,Field
class MyTableName(Model):
ID = Field(`索引`)
content = Field(`內容`)
則類名 MyTableName為儲存在指定資料庫中的資料集合名稱,ID為列物件,名稱為“索引”,以此類推,content也為列物件,名稱為“內容”。
可以按照自己的需求進行新增刪減列。
資料的儲存只需要在回撥函式中對對應的列物件進行賦值,而後呼叫ORM物件的save函式即可。比如在spider.py的爬蟲類中的成功回撥函式parse中儲存爬取到的資料:
...
def parse(self,response):
self.item.ID = 200
self.item.content = `這是內容`
self.item.save()
...
則 資料集合 MyTableName中會自動儲存一行資料:列“索引”為200,列“內容”為“這是內容”的資料行。引用orm資料模型物件只需要呼叫爬蟲類的item屬性,如上面示例中的self.item即是。
獲取其資料庫物件可以使用:self.item.db來獲得當前爬蟲連線的MongoDB資料庫物件。
可以通過
self.item.db.save()
self.item.db.delete()
self.item.db.update()
...
等api來實現資料庫操作。
事件迴圈loop
Amipy爬蟲的非同步請求基於python3的協程async框架,所以專案全程只有一個事件迴圈執行,如果需要新增更多的爬蟲請求,可以通過回撥函式傳進事件迴圈,加入請求佇列。
具體做法便是通過在爬蟲類的回撥函式中使用send函式來傳遞請求Request物件:
import amipy
from amipy import Request,send
class MySpider(amipy.Spider):
...
def parse(self,response):
...
# 加入新的爬蟲請求
url = `http://www.170mv.com/download/`
send(Request(self,url))
...
可以在專案配置檔案settings.py中設定整個專案最大的協程併發數CONCURRENCY,以及協程請求的延時等。
Telnet連線
Amipy爬蟲內建一個服務執行緒,可以通過Telnet進行連線來檢視操作當前專案的爬蟲,在啟動爬蟲後,可以通過新開一個DOS命令視窗,
輸入:
>telnet 127.0.0.1 2232
進行Telnet連線至專案服務執行緒,可以使用的命令有:
show spiders show all running spiders and their conditions.
list list a general situation of all spiders.
echo echo a running spider and its attributes.
pause pause a running spider by a give name.
stop stop a running/paused spider by a give name.
close close a spider by a give name.
restart restart a stopped spider by a give name.
resume resume a paused spider by a give name.
quit quit the Spider-Client.
help show all the available commands usage.
舉例,假設當前爬蟲唯一標識名稱為lianjia,則可以通過:
$amipy> pause lianjia
來暫停爬蟲lianjia的爬取進度,在爬蟲將當前請求佇列清空後會一直暫停,直到收到Telnet端發出的其他命令。恢復爬蟲使用:
$amipy> resume lianjia
檢視當前專案下所有爬蟲:
$amipy> list
詳細檢視則使用:
$amipy> show spiders
開啟關閉Telnet在專案的配置檔案settings.py中設定SPIDER_SERVER_ENABLE。
爬取去重
Amipy的爬取去重可以分為兩種:
- url去重
- 網頁內容正文去重
兩者皆使用了布隆過濾器去重,對於url去重,則是使用url+method+params+data的方式生成摘要進行布隆過濾器去重。
對於網頁正文去重則是按照配置檔案指定的正文檢測引數來檢測每個網頁的正文內容生成摘要存進布隆過濾器,可以在爬蟲的配置檔案
settings.py中對以下幾項進行配置來檢測網頁內容正文:
# 網頁內容剔除掉哪些標籤後再識別正文
BLOOMFILTER_HTML_EXTRACTS = [`script`,`style`,`head`]
# 允許一個正文內容塊中至多空行數
BLOOMFILTER_HTML_GAP = 3
# 連續多少行有正文則認為是一個正文塊
BLOOMFILTER_HTML_THRESHOLD = 5
# 每一行正文的字密度
BLOOMFILTER_HTML_DENSITY =45
上面兩種是預設的去重方式,還可以指定請求返回的網頁內容的某一部分作為響應指紋來進行鍼對性的去重。
如果想要自己指定哪個響應內容部分作為去重的指紋,可以在將請求Request送進協程佇列時指定指紋函式,如:
...
def parse(self,response):
...
send(Request(self,url,fingerprint=self.fingerprint))
...
def fingerprint(self,response):
...
# 返回需要作為指紋的文字字元等
return something
例子
1. 使用Amipy建立鏈家網爬蟲(LianJiaSpider)
爬蟲目的:爬取鏈家網上北京當前最新的租房資訊,包含“價格”,“房屋基本資訊”、“配套設施”、“房源描述”、“聯絡經紀人”、“地址和交通”存入MongoDB資料庫中
- 建立專案
進入到D:LianJia路徑,建立Amipy專案LJproject:
D:LianJia> amipy cproject LJproject
- 建立爬蟲
進入到專案路徑D:LianJiaLJproject,建立Amipy爬蟲lianjia:
D:LianJiaLJproject> amipy cspider lianjia
- 編寫資料庫模型
開啟D:LianJiaLJprojectspidersLianjiaitem.py,編寫資料儲存模型:
#coding:utf-8
from amipy.BaseClass.orm import Model,Field
class LianJiaRenting(Model):
price = Field(`價格`)
infos = Field(`房屋基本資訊`)
facility = Field(`配套設施`)
desc = Field(`房源描述`)
agent = Field(`聯絡經紀人`)
addr = Field(`地址與交通`)
- 設定資料庫連線
開啟 D:LianJiaLJprojectspidersLianjiasettings.py,找到MongoDB資料庫連線設定,進行設定:
# MongoDB settings for data saving.
DATABASE_SETTINGS = {
`host`:`127.0.0.1`,
`port`:27017,
`user`:``,
`password`:``,
`database`:`LianJiaDB`,
}
要先確保系統安裝好MongoDB資料庫並已經開啟了服務。
- 編寫爬蟲指令碼
開啟 D:LianJiaLJprojectspidersLianjiaspider.py,編寫爬蟲採集指令碼:
import amipy,re
from amipy import send,Request,Url
from bs4 import BeautifulSoup as bs
class LianjiaSpider(amipy.Spider):
name = `lianjia`
# 設定爬取初始連結
urls = [`https://bj.lianjia.com/zufang/`]
# 設定爬蟲白名單,只允許爬取匹配的連結
whitelist = [
Url(re=`https://bj.lianjia.com/zufang/.*`),
]
# 自定義的屬性
host =`https://bj.lianjia.com`
page = 1
# 請求成功回撥函式
def parse(self,response):
soup = bs(response.text(),`lxml`)
item_list = soup(`div`,class_=`content__list--item`)
for i in item_list:
# 獲取詳情頁連結 併傳送至爬蟲請求佇列
url = self.host+i.a[`href`]
send(Request(self,url,callback=self.details))
# 新增下一頁
totalpage = soup(`div`,class_=`content__pg`)[0][`data-totalpage`]
if self.page>=int(totalpage):
return
self.page +=1
send(Request(self,self.host+`/zufang/pg{}/`.format(self.page)))
def details(self,response):
infos = {}
agent = {}
facility = []
soup = bs(response.text(),`lxml`)
infos_li = soup(`div`,class_=`content__article__info`)[0].ul(`li`)
facility_li = soup(`ul`,class_=`content__article__info2`)[0](`li`)
agent_ul = soup(`ul`,id=`agentList`)[0]
addr_li = soup(`div`,id=`around`)[0].ul.li
desc_li = soup(`div`,id=`desc`)[0].li
desc_li.div.extract()
desc = desc_li.p[`data-desc`] if desc_li.p else ``
for i in infos_li:
text = i.text
if `:` in text:
infos.update({text.split(`:`)[0]:text.split(`:`)[1]})
for i in facility_li[1:]:
if `_no` not in i[`class`][-2]:
facility.append(i.text)
for div in agent_ul(`div`,class_=`desc`):
name = div.a.text
phone = div(`div`,class_=`phone`)[0].text
agent[name]=phone
# 資料模型對應並儲存
self.item.desc = desc
self.item.addr = re.sub(r`[
]`,``,addr_li.text) if addr_li else ``
self.item.price = soup(`p`,class_=`content__aside--title`)[0].text
self.item.infos = infos
self.item.agent = agent
self.item.facility = facility
self.item.save()
如果在爬蟲配置檔案settings.py中設定遵守目標網站機器人協議可能會被禁止採集,可以自行關閉設定。
另外,開啟網頁內容相似過濾BLOOMFILTER_HTML_ON可能會使爬取的結果數較少,爬蟲只會採集相似度不同的網頁內容的連結,
如果需要大批量採集,而網頁正文較少的,可以關閉這個設定。
程式碼比較粗糙,但可以知道Amipy爬蟲基本的實現流程。
- 執行爬蟲
在專案根路徑下,輸入:
D:LianJiaLJproject> amipy runspider
- 檢視資料庫
進入MongoDB資料庫:可以看到在資料庫‘LianJiaDB’下的集合“LianJiaRenting”中已經儲存有我們爬取的資料,格式如下:
{
"_id" : ObjectId("5c6541b065b2fd1cf002c565"),
"價格" : "7500元/月 (季付價)",
"房屋基本資訊" : {
"釋出" : "20天前",
"入住" : "隨時入住",
"租期" : "2~3年",
"看房" : "暫無資料",
"樓層" : "中樓層/6層",
"電梯" : "無",
"車位" : "暫無資料",
"用水" : "民水",
"用電" : "民電",
"燃氣" : "有",
"採暖" : "集中供暖"
},
"配套設施" : [
"電視",
"冰箱",
"洗衣機",
"空調",
"熱水器",
"床",
"暖氣",
"寬頻",
"衣櫃",
"天然氣"
],
"房源描述" : "【交通出行】 小區門口為八里莊南里公交車站,75,675等多路公交經過。地鐵6號線十里堡站D口,距離地鐵口400米,交通十分方便,便於出行。<br />
【周邊配套】 此房位置棒棒噠,有建設銀行,中國銀行,交通銀行,郵政儲蓄,果多美水果超市,購物,金旭菜市場,娛樂,休閒,便利。旁邊首航超市,姥姥家春餅,味多美蛋糕店,生活方便。<br />
【小區介紹】 該小區中此樓是1981建成,安全舒適,小區內主力樓盤為6層板樓,前後無遮擋,此樓是多見的板樓,樓層高視野好。<br />
",
"聯絡經紀人" : {
"宋玉恆" : "4000124028轉7907"
},
"地址與交通" : "距離6號線-十里堡192m"
}
- 檢視當前爬取進度
新開一個DOS埠,輸入:
> telnet 127.0.0.1 2232
進行Telnet連線,可以使用命令操作檢視當前爬蟲的爬取狀態。例如使用echo命令:
$amipy> echo lianjia
可以檢視當前爬蟲的狀態:
----------------Spider-lianjia-------------------
- Name:lianjia Status:RUNNING
- Class:LianjiaSpider
- Success:25 Fail:0 Exception:0
- Priority:0
- SeedUrls:[`https://bj.lianjia.com/zufang/`]
- Path:D:LianJiaLJprojectspidersLianjia
- Session:<aiohttp.client.ClientSession object at 0x000000000386FE10>
- StartAt:Thu Feb 14 20:30:21 2019
- PausedAt:None
- ResumeAt:None
- StopAt:None
- RestartAt:None
- CloseAt:None
--------------------------------------------------