一個簡單的爬蟲實例
今天我們來簡單完成一個網(wǎng)站爬蟲例子,體驗下基本的爬蟲工作流程。在有這個體驗之后,才能更好地理解框架的重要性。
1. 網(wǎng)絡(luò)爬蟲之網(wǎng)站分析
這一節(jié)我們來爬取一個圖書網(wǎng)站:互動出版網(wǎng)。之所以選擇這個網(wǎng)站,主要是它的數(shù)據(jù)比較好爬取,沒有反爬蟲機制,且網(wǎng)站的結(jié)構(gòu)也不復(fù)雜,比較適合作為菜鳥進行練手。我們首先來分析網(wǎng)站及其相關(guān)的 HTML 元素,確定要爬取的內(nèi)容。
互動出版網(wǎng)的網(wǎng)站首頁如下:
可以看到,這個網(wǎng)站沒有用到 https,依舊使用的是 http 協(xié)議,這個網(wǎng)站是極不安全的。我們現(xiàn)在要爬取的是這個網(wǎng)站的計算機類的圖書,我們可以點擊全部圖書分類那里,得到所有圖書的分類情況。
通過 F12 可以看到,每個計算機的分類對應(yīng)著一個鏈接。我們點進去看就會得到對應(yīng)分類下的圖書列表,還帶著分頁信息:
從這個頁面中,我們可以分析到很多,首先對于一個圖書信息,我們想要提取的數(shù)據(jù)有:
- 圖書標(biāo)題;
- 圖書作者;
- 出版社;
- ISBN;
- 出版時間;
- 圖書價格。
至于圖書的詳情頁面我們就不再進去看了,詳情頁中能到到更多信息,比如總頁數(shù)、圖書簡介、目錄等等。此外,這里有一個分頁信息,通過多次點擊可以發(fā)現(xiàn),只是前面的 url 中的一個數(shù)字發(fā)生了變化,因此我們可以直接構(gòu)造出相應(yīng)頁數(shù)的 url 請求,獲取其他頁的圖書列表、還等什么呢?開始激動人心的圖書數(shù)據(jù)爬取流程吧?。?!
2. 網(wǎng)絡(luò)爬蟲之爬取流程
根據(jù)上面的分析,我們來設(shè)計相應(yīng)的爬取流程,總體上有如下幾個步驟:
獲取計算機圖書下的分類列表,包括對應(yīng)的 URL。我們可以實現(xiàn)一個函數(shù)專門請求分類頁面,然后提取相應(yīng)的 URL 列表:
專門完成一個函數(shù),讀取計算機分類下的圖書列表。通過不斷的分頁查詢將這個分類下的所有圖書信息全部抓取到。
http://product.china-pub.com/cache/browse2/59/{頁號}_1_59-{分類編號}_0.html
分類編號不用管,我們會自行提取 URL。而對于請求的頁號,可以自行設(shè)定,從1開始請求,每次加1,直到請求的 URL 返回 404 時,表明這個分類下的圖書列表請求完畢,然后就可以進行下一個分類的請求了。
最后完成一個簡單的保存函數(shù),保存采集到的數(shù)據(jù)庫。這個就比較簡單了,我們直接將采集到的圖書信息成批地保存到 MongoDB 數(shù)據(jù)庫中。
3. 圖書爬蟲之代碼實現(xiàn)
根據(jù)上面的分析,我們來實現(xiàn)相應(yīng)的代碼。首先是完成獲取計算機的所有分類以及相應(yīng)的 URL 地址:
def get_all_computer_book_urls(page_url):
"""
獲取所有計算機分類圖書的url地址
:return:
"""
response = requests.get(url=page_url, headers=headers)
if response.status_code != 200:
return [], []
response.encoding = 'gbk'
tree = etree.fromstring(response.text, etree.HTMLParser())
# 提取計算機分類的文本列表
c = tree.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/text()")
# 提取計算機分類的url列表
u = tree.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/@href")
return c, u
我們簡單測試下這個函數(shù):
[store@server2 chap06]$ python3
Python 3.6.8 (default, Apr 2 2020, 13:34:55)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from china_pub_crawler import get_all_computer_book_urls
>>> get_all_computer_book_urls('http://www.china-pub.com/Browse/')
(['IT圖書網(wǎng)絡(luò)出版 [59-00]', '計算機科學(xué)理論與基礎(chǔ)知識 [59-01]', '計算機組織與體系結(jié)構(gòu) [59-02]', '計算機網(wǎng)絡(luò) [59-03]', '安全 [59-04]', '軟件與程序設(shè)計 [59-05]', '軟件工程及軟件方法學(xué) [59-06]', '操作系統(tǒng) [59-07]', '數(shù)據(jù)庫 [59-08]', '硬件與維護 [59-09]', '圖形圖像、多媒體、網(wǎng)頁制作 [59-10]', '中文信息處理 [59-11]', '計算機輔助設(shè)計與工程計算 [59-12]', '辦公軟件 [59-13]', '專用軟件 [59-14]', '人工智能 [59-15]', '考試認(rèn)證 [59-16]', '工具書 [59-17]', '計算機控制與仿真 [59-18]', '信息系統(tǒng) [59-19]', '電子商務(wù)與計算機文化 [59-20]', '電子工程 [59-21]', '期刊 [59-22]', '游戲 [59-26]', 'IT服務(wù)管理 [59-27]', '計算機文化用品 [59-80]'], ['http://product.china-pub.com/cache/browse2/59/1_1_59-00_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-01_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-02_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-03_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-04_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-05_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-06_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-07_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-08_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-09_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-10_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-11_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-12_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-13_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-14_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-15_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-16_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-17_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-18_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-19_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-20_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-21_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-22_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-26_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-27_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-80_0.html'])
可以看到這個函數(shù)已經(jīng)實現(xiàn)了我們想要的結(jié)果。接下來我們要完成一個函數(shù)來獲取對應(yīng)分類下的所有圖書信息,不過在此之前,我們需要先完成解析單個圖書列表頁面的方法:
def parse_books_page(html_data):
books = []
tree = etree.fromstring(html_data, etree.HTMLParser())
result_tree = tree.xpath("http://div[@class='search_result']/table/tr/td[2]/ul")
for result in result_tree:
try:
book_info = {}
book_info['title'] = result.xpath("./li[@class='result_name']/a/text()")[0]
book_info['book_url'] = result.xpath("./li[@class='result_name']/a/@href")[0]
info = result.xpath("./li[2]/text()")[0]
book_info['author'] = info.split('|')[0].strip()
book_info['publisher'] = info.split('|')[1].strip()
book_info['isbn'] = info.split('|')[2].strip()
book_info['publish_date'] = info.split('|')[3].strip()
book_info['vip_price'] = result.xpath("./li[@class='result_book']/ul/li[@class='book_dis']/text()")[0]
book_info['price'] = result.xpath("./li[@class='result_book']/ul/li[@class='book_price']/text()")[0]
# print(f'解析出的圖書信息為:{book_info}')
books.append(book_info)
except Exception as e:
print("解析數(shù)據(jù)出現(xiàn)異常,忽略!")
return books
上面的函數(shù)主要解析的是一頁圖書列表數(shù)據(jù),同樣基于 xpath 定位相應(yīng)的元素,然后提取我們想要的數(shù)據(jù)。其中由于部分信息合在一起,我們在提取數(shù)據(jù)后還要做相關(guān)的處理,分別提取對應(yīng)的信息。我們可以從網(wǎng)頁中直接樣 HTML 拷貝下來,然后對該函數(shù)進行測試:
我們把保存的網(wǎng)頁命名為 test.html
,放到與該代碼同級的目錄下,然后進入命令行操作:
>>> from china_pub_crawler import parse_books_page
>>> f = open('test.html', 'r+')
>>> html_content = f.read()
>>> parse_books_page(html_content)
[{'title': '(特價書)零基礎(chǔ)學(xué)ASP.NET 3.5', 'book_url': 'http://product.china-pub.com/216269', 'author': '王向軍;王欣惠 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111261414', 'publish_date': '2009-02-01出版', 'vip_price': 'VIP會員價:', 'price': '¥58.00'}, {'title': 'Objective-C 2.0 Mac和iOS開發(fā)實踐指南(原書第2版)', 'book_url': 'http://product.china-pub.com/3770704', 'author': '(美)Robert Clair (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111484561', 'publish_date': '2015-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥79.00'}, {'title': '(特價書)ASP.NET 3.5實例精通', 'book_url': 'http://product.china-pub.com/216272', 'author': '王院峰 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111259794', 'publish_date': '2009-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥55.00'}, {'title': '(特價書)CSS+HTML語法與范例詳解詞典', 'book_url': 'http://product.china-pub.com/216275', 'author': '符旭凌 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111263647', 'publish_date': '2009-02-01出版', 'vip_price': 'VIP會員價:', 'price': '¥39.00'}, {'title': '(特價書)Java ME 游戲編程(原書第2版)', 'book_url': 'http://product.china-pub.com/216296', 'author': '(美)Martin J. Wells; John P. Flynt (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111264941', 'publish_date': '2009-03-01出版', 'vip_price': 'VIP會員價:', 'price': '¥49.00'}, {'title': '(特價書)Visual Basic實例精通', 'book_url': 'http://product.china-pub.com/216304', 'author': '柴相花 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111263296', 'publish_date': '2009-04-01出版', 'vip_price': 'VIP會員價:', 'price': '¥59.80'}, {'title': '高性能電子商務(wù)平臺構(gòu)建:架構(gòu)、設(shè)計與開發(fā)[按需印刷]', 'book_url': 'http://product.china-pub.com/3770743', 'author': 'ShopNC產(chǎn)品部 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111485643', 'publish_date': '2015-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥79.00'}, {'title': '[套裝書]Java核心技術(shù) 卷Ⅰ 基礎(chǔ)知識(原書第10版)+Java核心技術(shù) 卷Ⅱ高級特性(原書第10版)', 'book_url': 'http://product.china-pub.com/7008447', 'author': '(美)凱S.霍斯特曼(Cay S. Horstmann)????(美)凱S. 霍斯特曼(Cay S. Horstmann) (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787007008447', 'publish_date': '2017-08-01出版', 'vip_price': 'VIP會員價:', 'price': '¥258.00'}, {'title': '(特價書)Dojo構(gòu)建Ajax應(yīng)用程序', 'book_url': 'http://product.china-pub.com/216315', 'author': '(美)James E.Harmon (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111266648', 'publish_date': '2009-05-01出版', 'vip_price': 'VIP會員價:', 'price': '¥45.00'}, {'title': '(特價書)編譯原理第2版.本科教學(xué)版', 'book_url': 'http://product.china-pub.com/216336', 'author': '(美)Alfred V. Aho;Monica S. Lam;Ravi Sethi;Jeffrey D. Ullman (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111269298', 'publish_date': '2009-05-01出版', 'vip_price': 'VIP會員價:', 'price': '¥55.00'}, {'title': '(特價書)用Alice學(xué)編程(原書第2版)', 'book_url': 'http://product.china-pub.com/216354', 'author': '(美)Wanda P.Dann;Stephen Cooper;Randy Pausch (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111274629', 'publish_date': '2009-07-01出版', 'vip_price': 'VIP會員價:', 'price': '¥39.00'}, {'title': 'Java語言程序設(shè)計(第2版)', 'book_url': 'http://product.china-pub.com/50051', 'author': '趙國玲;王宏;柴大鵬 (著)', 'publisher': '機械工業(yè)出版社*', 'isbn': '9787111297376', 'publish_date': '2010-03-01出版', 'vip_price': 'VIP會員價:', 'price': '¥32.00'}, {'title': '從零開始學(xué)Python程序設(shè)計', 'book_url': 'http://product.china-pub.com/7017939', 'author': '吳惠茹 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111583813', 'publish_date': '2018-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥79.00'}, {'title': '(特價書)匯編語言', 'book_url': 'http://product.china-pub.com/216385', 'author': '鄭曉薇 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111269076', 'publish_date': '2009-09-01出版', 'vip_price': 'VIP會員價:', 'price': '¥29.00'}, {'title': '(特價書)Visual Basic.NET案例教程', 'book_url': 'http://product.china-pub.com/216388', 'author': '馬玉春;劉杰民;王鑫 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111272571', 'publish_date': '2009-09-01出版', 'vip_price': 'VIP會員價:', 'price': '¥30.00'}, {'title': '小程序從0到1:微信全棧工程師一本通', 'book_url': 'http://product.china-pub.com/7017943', 'author': '石橋碼農(nóng) (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111584049', 'publish_date': '2018-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥59.00'}, {'title': '深入分布式緩存:從原理到實踐', 'book_url': 'http://product.china-pub.com/7017945', 'author': '于君澤 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111585190', 'publish_date': '2018-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥99.00'}, {'title': '(特價書)ASP.NET AJAX服務(wù)器控件高級編程(.NET 3.5版)', 'book_url': 'http://product.china-pub.com/216397', 'author': '(美)Adam Calderon;Joel Rumerman (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111270966', 'publish_date': '2009-09-01出版', 'vip_price': 'VIP會員價:', 'price': '¥65.00'}, {'title': 'PaaS程序設(shè)計', 'book_url': 'http://product.china-pub.com/3770830', 'author': '(美)Lucas Carlson (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111482451', 'publish_date': '2015-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥39.00'}, {'title': 'Visual C++數(shù)字圖像處理[按需印刷]', 'book_url': 'http://product.china-pub.com/2437', 'author': '何斌 馬天予 王運堅 朱紅蓮 (著)', 'publisher': '人民郵電出版社', 'isbn': '711509263X', 'publish_date': '2001-04-01出版', 'vip_price': 'VIP會員價:', 'price': '¥72.00'}]
是不是能正確提取圖書列表的相關(guān)信息?這也說明我們的函數(shù)的正確性,由于也可能在解析中存在一些異常,比如某個字段的缺失,我們需要捕獲異常并忽略該條數(shù)據(jù),讓程序能繼續(xù)走下去而不是停止運行。
在完成了上述的工作后,我們來通過對頁號的 URL 構(gòu)造,實現(xiàn)采集多個分頁下的數(shù)據(jù),最后達到讀取完該分類下的所有圖書信息的目的。完整代碼如下:
def get_category_books(category, url):
"""
獲取類別圖書,下面會有分頁,我們一直請求,直到分頁請求返回404即可停止
:return:
"""
books = []
page = 1
regex = "(http://.*/)([0-9]+)_(.*).html"
pattern = re.compile(regex)
m = pattern.match(url)
if not m:
return []
prefix_path = m.group(1)
current_page = m.group(2)
if current_page != 1:
print("提取數(shù)據(jù)不是從第一行開始,可能存在問題")
suffix_path = m.group(3)
current_page = page
while True:
# 構(gòu)造分頁請求的URL
book_url = f"{prefix_path}{current_page}_{suffix_path}.html"
response = requests.get(url=book_url, headers=headers)
print(f"提取分類[{category}]下的第{current_page}頁圖書數(shù)據(jù)")
if response.status_code != 200:
print(f"[{category}]該分類下的圖書數(shù)據(jù)提取完畢!")
break
response.encoding = 'gbk'
# 將該分頁的數(shù)據(jù)加到列表中
books.extend(parse_books_page(response.text))
current_page += 1
# 一定要緩一緩,避免對對方服務(wù)造成太大壓力
time.sleep(0.5)
return books
最后保存數(shù)據(jù)到 MongoDB 中,這一步非常簡單,我們前面已經(jīng)操作過 MongoDB 的文檔插入,直接搬用即可:
client = pymongo.MongoClient(host='MongoDB的服務(wù)地址', port=27017)
client.admin.authenticate("admin", "shencong1992")
db = client.scrapy_manual
collection = db.china_pub
# ...
def save_to_mongodb(data):
try:
collection.insert_many(data)
except Exception as e:
print("批量插入數(shù)據(jù)異常:{}".format(str(e)))
正是由于我們前面生成了批量的 json 數(shù)據(jù),這里直接使用集合的 insert_many()
方法即可對采集到的數(shù)據(jù)批量插入 MongoDB 中。代碼的最后我們加上一個 main 函數(shù)即可:
# ...
if __name__ == '__main__':
page_url = "http://www.china-pub.com/Browse/"
categories, urls = get_all_computer_book_urls(page_url)
# print(categories)
books_total = {}
for i in range(len(urls)):
books_category_data = get_category_books(categories[i], urls[i])
print(f"保存[{categories[i]}]圖書數(shù)據(jù)到mongodb中")
save_to_mongodb(books_category_data)
print("爬取互動出版網(wǎng)的計算機分類數(shù)據(jù)完成")
這樣一個簡單的爬蟲就完成了,還等什么,開始跑起來吧?。?/p>
4. 圖書爬蟲之代碼運行
這是一個簡單的爬蟲,代碼全在一個 python 文件中,我們直接單機運行即可,下面看在我本機上的演示效果:
最后我們從互動出版網(wǎng)上一共獲取了9010條圖書記錄。這是我們的第一次嘗試,是不是覺得爬蟲很簡單,很有趣?只不過我們爬取的這個網(wǎng)站會比較簡單,而且沒有設(shè)置相應(yīng)的反爬蟲機制。后面我們會實戰(zhàn)更多更有難度的網(wǎng)站,讓這門課程更加有趣和有用。
5. 小結(jié)
本小節(jié)我們以互動出版網(wǎng)為例,分析了手工爬取一個網(wǎng)站所需要進行的步驟,并完成了互動出版網(wǎng)上計算機類的圖書的數(shù)據(jù)爬取。接下來我們會用 scrapy 框架來實現(xiàn)這樣一個簡易的爬蟲,體驗 Scrapy 框架給我們帶來的高效的開發(fā)模式和優(yōu)異的性能。