我的第一個完整的小說爬蟲
紀念我的第一個爬蟲程式,一共寫了三個白天,其中有兩個上午沒有看,中途遇到了各種奇怪的問題,伴隨著他們的解決,對於一些基本的操作也弄清楚了。果然,對於這些東西的最號的學習方式,就是在使用中學習,通過解決問題的方式來搞定這些知識。按需索取,才能更有針對性。
大體記錄下整個過程。
準備構思
出於對於python的熱愛,想要嘗試一些練手的專案,但是不論是看書,還是直接嘗試別人的專案,到最後都會淪為不停地複製貼上…最實際的就是自己來上手親自寫程式碼。思路都是一樣的,但是具體的實現還得靠自己。
以前的複製貼上給我的幫助也就是告訴了我大致的流程。
確定目標網址
目標網址是關鍵。我夢想中的爬蟲是那種偏向於更智慧的,直接給他一個想要獲取的關鍵詞,一步步的流程直接自己完成,可以自己給定範圍,也可以直接爬取整個網際網路或者更實際的就是整個百度上的內容,但是,目前就我而言,見到的爬蟲,都是給定目標網址,通過目標頁面上的內容進一步執行規定的操作,所以現在來看,我們在寫爬蟲之前,需要確定一個基準頁面,這個是需要我們事先制定的。在考慮我們需要程式完成怎樣的功能,獲取頁面文字還是相關連結內容還是其他的目的。
我這個程式想要獲取的是《劍來》小說,把各個章節的內容爬去下載儲存到檔案裡。
程式設計只是實現目的的工具。
所以重點是分析我們的需求。
獲取小說目錄頁面是基本。這裡有各個章節的連結,標題等等內容。這是我們需要的。
有了各個章節的連結,就需要進入其中獲得各個章節的內容。
所以,我們需要獲得頁面內容,需要從中獲得目標內容。
所以使用 urllib.request
,re
庫。
前者用來獲得網頁內容,後者獲得目標資訊。
headers
直接使用urllib.request
的urlopen()
,read()
方法是會報類似以下的錯誤(這裡是網上查詢過來的,都是類似的):
raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
HTTPError: HTTP Error 403: Forbidden
出現urllib2.HTTPError: HTTP Error 403: Forbidden
錯誤是由於網站禁止爬蟲,可以在請求加上頭資訊,偽裝成瀏覽器。
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0'} request = url_req.Request(url, headers=headers) response = url_req.urlopen(request, data=None, timeout=3) html = response.read().decode('GBK')
注意:這裡存在兩個容易出問題的地方。
- 編碼:編碼問題是使用爬蟲中有時候會很頭痛的問題,由於網頁原始碼編碼格式不明確,所以這裡嘗試了許久。
使用chardet
庫的detect()
方法可以檢測位元組字串的編碼。所以直接檢測這裡的html(先不要解碼)。輸出的是GB2312
,但是在後面頁面的爬取中,會出現提示有的字元的編碼異常,所以這裡採取了比其範圍更廣的中文字符集GBK
,解決了這個問題。
- 設定超時範圍:由於頻繁的獲取網頁內容,目標網站有時候會出現沒有響應的問題。
(這個問題可以見我在CSDN上的提問:關於python爬蟲程式中途停止的問題)
於是我採取了捕獲 urlopen()
的socket.timeout
異常,並在出現異常的時候再迴圈訪問,直到獲得目標頁面。
獲得目標內容
這裡使用的是正則表示式。re
模組。這裡的使用並不複雜。
首先需要一個模式字串。以re.I
指定忽略大小寫,編譯後的物件擁有本身匹配的方法,這裡使用的是findall()
,返回一個所有結果組成的列表。可以及時返回輸出其內容,進而選擇合適的部分進行處理。
通過檢視相關的符號,這裡使用(.+?)
來實現匹配非貪婪模式(儘量少的)下任意無限字元,對之使用()
,進而匹配括號內的模式。
檔案寫入
使用with open() as file:
,進而可以處理檔案。並且可以自動執行開啟和關閉檔案,更為便捷安全。
with open(findall_title[0] + '.txt', 'w+', encoding='utf-8') as open_file:
- 這裡也要注意編碼的問題,指定
utf-8
。會避免一些問題。 - 這裡使用
w+
模式,追加寫檔案。
完整程式碼
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 11 16:31:42 2017
@author: lart
"""
import urllib.request as url_req
import re, socket, time
def r_o_html(url):
print('r_o_html begin')
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0'}
request = url_req.Request(url, headers=headers)
NET_STATUS = False
while not NET_STATUS:
try:
response = url_req.urlopen(request, data=None, timeout=3)
html = response.read().decode('GBK')
print('NET_STATUS is good')
print('r_o_html end')
return html
except socket.timeout:
print('NET_STATUS is not good')
NET_STATUS = False
def re_findall(re_string, operation, html):
print('re_findall begin')
pattern = re.compile(re_string, re.I)
if operation == 'findall':
result = pattern.findall(html)
else:
print('this operation is invalid')
exit(-1)
print('re_findall end')
return result
if __name__ == '__main__':
url_base = 'http://www.7kankan.la/book/1/'
html = r_o_html(url_base)
findall_title = re_findall(r'<title>(.+?)</title>', 'findall', html)
findall_chapter = re_findall(r'<dd class="col-md-3"><a href=[\',"](.+?)[\',"] title=[\',"](.+?)[\',"]>', 'findall', html)
with open(findall_title[0] + '.txt', 'w+', encoding='utf-8') as open_file:
print('article檔案開啟', findall_chapter)
for i in range(len(findall_chapter)):
print('第' + str(i) + '章')
open_file.write('\n\n\t' + findall_chapter[i][1] + '\n --------------------------------------------------------------------- \n')
url_chapter = url_base + findall_chapter[i][0]
html_chapter = r_o_html(url_chapter)
findall_article = re_findall(r' (.+?)<br />', 'findall', html_chapter)
findall_article_next = findall_chapter[i][0].replace('.html', '_2.html')
url_nextchapter = url_base + findall_article_next
html_nextchapter = r_o_html(url_nextchapter)
if html_nextchapter:
findall_article.extend(re_findall(r' (.+?)<br />', 'findall', html_nextchapter))
for text in findall_article:
open_file.write(text + '\n')
time.sleep(1)
print('檔案寫入完畢')