Python爬蟲(圖片)編寫過程當中遇到的問題

  最近我忽然對網絡爬蟲開竅了,真正作起來的時候發現並不算太難,都怪我之前有點懶,不過近兩年編寫了一些程序,手感積累了一些確定也是因素,總之,仍是慚愧了。好了,說正題,我把這兩天作爬蟲的過程當中遇到的問題總結一下:html

  需求:作一個爬蟲,爬取一個網站上全部的圖片(只爬大圖,小圖標就略過)python

  思路:一、獲取網站入口,這個入口網頁上有不少圖片集合入口,進入這些圖片集合就能看到圖片連接了,因此爬取的深度爲2,比較簡單;二、各個子圖片集合內所包含的圖片連接有兩種形式:一種是絕對圖片路徑(直接下載便可),另外一種的相對圖片路徑(須要自行拼接當前網頁路徑)。總之這些子圖集合的表現形式簡單,沒有考慮更復雜的狀況。三、在爬取的過程當中保存已成功爬取的路徑,防止每次爬取都執行重複的任務,固然,當每次啓動時要首先加載該歷史數據庫,剩下的就是細節了。正則表達式

  快速連接:數據庫

2.1 日誌系統瀏覽器

2.2 記錄訪問歷史網絡

2.3 異常處理app

2.4 網頁自動編碼判斷ide

2.5 遠程主機重置連接(Errno 10054)函數

2.6 使用BeautifulSoup來分析網頁post

1、所有代碼

  直接先來代碼,再詳細說優化的過程和內容吧:

  1 __author__ = 'KLH'
  2 # -*- coding:utf-8 -*-
  3 
  4 import urllib
  5 import urllib2
  6 import chardet
  7 import re
  8 import os
  9 import time
 10 from myLogger import *
 11 
 12 # 網絡蜘蛛
 13 class Spider:
 14     
 15     # 類初始化
 16     def __init__(self):
 17         self.contentFolder = u"抓取內容"
 18         self.dbName = "url.db"
 19         self.createFolder(self.contentFolder)
 20         self.urlDB = set()
 21         
 22     # 獲取URL數據庫以獲取爬過的網頁地址
 23     def loadDatabase(self):
 24         isExists = os.path.exists(self.dbName)
 25         if not isExists:
 26             logging.info(u"建立URL數據庫文件:'" + self.dbName + u"'")
 27             f = open(self.dbName, 'w+')
 28             f.write("#Create time: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + '\n')
 29             f.close()
 30             return
 31         db = open(self.dbName, 'r')
 32         for line in db.readlines():
 33             if not line.startswith('#'):
 34                 self.urlDB.add(line.strip('\n'))
 35         db.close()
 36         logging.info(u"URL數據庫加載完成!")
 37     
 38     # 追加數據庫文件
 39     def writeToDatabase(self, url):
 40         db = open(self.dbName, 'a')
 41         db.write(url + '\n')
 42         db.close()
 43         
 44     # 處理路徑名稱中的空格字符    
 45     def getPathName(self, pathName):
 46         newName = ""
 47         subName = pathName.split()
 48         i = 0
 49         while i < len(subName) - 1:
 50             newName = newName + subName[i]
 51             i = i + 1
 52         return newName
 53 
 54     # 獲取索引頁面的內容
 55     def getPage(self, pageURL, second):
 56         try:
 57             headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'}         
 58             request = urllib2.Request(pageURL, headers = headers)
 59             response = urllib2.urlopen(request, timeout = second)
 60             data = response.read()
 61             response.close()
 62             return data.decode('gbk'), True
 63         except urllib2.HTTPError,e:    #HTTPError必須排在URLError的前面
 64             logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read())
 65             return "", False
 66         except urllib2.URLError, e:
 67             logging.error("URLError reason:" + str(e.reason) + " - " + str(e))
 68             return "", False
 69         except Exception, e:
 70             logging.error(u"獲取網頁失敗:" + str(e))
 71             return "", False
 72 
 73     # 獲取索引界面全部子頁面信息,list格式
 74     def getContents(self, pageURL, second):
 75         contents = []
 76         page, succeed = self.getPage(pageURL, second)
 77         if succeed:
 78             # 這裏的正則表達式很重要,決定了第一步的抓取內容:
 79             pattern = re.compile('<tr>.*?<a href="(.*?)".*?<b>(.*?)</b>.*?</tr>',re.S)
 80             items = re.findall(pattern,page)
 81             for item in items:
 82                 contents.append([item[0],item[1]])    
 83         contents.sort()
 84         return contents
 85    
 86     # 獲取頁面全部圖片
 87     def getAllImgURL(self, infoURL):
 88         images = []
 89         succeed = True
 90         try:
 91             headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'}         
 92             request = urllib2.Request(infoURL, headers = headers)
 93             data = urllib2.urlopen(request).read()
 94             chardet1 = chardet.detect(data)     # 自動判斷網頁編碼
 95             page = data.decode(str(chardet1['encoding']))   
 96             
 97             # 第一種解碼格式:
 98             pattern = re.compile('<option value="(.*?)">(.*?)</option>')
 99             items = re.findall(pattern, page)
100             # item[0]爲圖片URL尾部,item[1]爲圖片名稱
101             for item in items:
102                 if item.startswith('http://'):
103                     imageURL = item[0]
104                     if imageURL in self.urlDB:
105                         logging.info(u"得到圖片URL(曾被訪問,跳過):" + imageURL)
106                     else:
107                         logging.info(u"得到圖片URL:" + imageURL)
108                         images.append(imageURL)
109                 else:
110                     imageURL = infoURL + item[0]
111                     if imageURL in self.urlDB:
112                         logging.info(u"得到圖片URL(曾被訪問,跳過):" + imageURL)
113                     else:
114                         logging.info(u"得到圖片URL:" + imageURL)
115                         images.append(imageURL) 
116                     
117             # 第二種解碼格式
118             pattern = re.compile('<IMG src="(.*?)".*?>')
119             items = re.findall(pattern, page)
120             # item爲圖片URL
121             for item in items:
122                 if item.startswith('http://'):
123                     if item in self.urlDB:
124                         logging.info(u"得到圖片URL(曾被訪問,跳過):" + item)
125                     else:
126                         logging.info(u"得到圖片URL:" + item)
127                         images.append(item)             
128                 
129         except Exception, e:
130             logging.warning(u"在獲取子路徑圖片列表時出現異常:" + str(e))
131             succeed = False
132         return images, succeed
133 
134     # 保存全部圖片
135     def saveImgs(self, images, name):
136         logging.info(u'發現"' + name + u'"共有' + str(len(images)) + u"張照片")
137         allSucceed = True
138         for imageURL in images:
139             splitPath = imageURL.split('/')
140             fTail = splitPath.pop()
141             fileName = name + "/" + fTail
142             logging.info(u"開始準備保存圖片(超時設置:120秒):" + imageURL)
143             startTime = time.time()
144             succeed = self.saveImg(imageURL, fileName, 120)
145             spanTime = time.time() - startTime
146             if succeed:
147                 logging.info(u"保存圖片完成(耗時:" + str(spanTime) + u"秒):" + fileName)
148                 # 保存文件存儲記錄
149                 self.urlDB.add(imageURL)
150                 self.writeToDatabase(imageURL)                   
151             else:
152                 logging.warning(u"保存圖片失敗(耗時:" + str(spanTime) + u"秒):" + imageURL) 
153                 allSucceed = False
154             # 爲了防止網站封殺,這裏暫停1秒
155             time.sleep(1)
156         return allSucceed
157 
158     # 傳入圖片地址,文件名,超時時間,保存單張圖片
159     def saveImg(self, imageURL, fileName, second):
160         try:            
161             headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'}
162             request = urllib2.Request(imageURL, headers = headers)
163             u = urllib2.urlopen(request, timeout = second) 
164             data = u.read()
165             f = open(fileName, 'wb')
166             f.write(data)
167             f.close()
168             u.close()
169             return True
170         except urllib2.HTTPError,e:    #HTTPError必須排在URLError的前面
171             logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read())
172             return False
173         except urllib2.URLError, e:
174             logging.error("URLError reason:" + str(e.reason) + " - " + str(e))
175             return False
176         except Exception, e:
177             logging.error(u"保存圖片失敗:" + str(e))
178             return False
179 
180     # 建立新目錄
181     def createFolder(self, path):
182         path = path.strip()
183         # 判斷路徑是否存在
184         isExists=os.path.exists(path)
185         # 判斷結果
186         if not isExists:
187             # 若是不存在則建立目錄
188             logging.info(u"建立文件夾:'" + path + u"'")
189             # 建立目錄操做函數
190             os.makedirs(path)
191             return True
192         else:
193             # 若是目錄存在則不建立,並提示目錄已存在
194             logging.info(u"名爲'" + path + u"'的文件夾已經存在,跳過")
195             return False
196  
197     # 獲取的首頁地址
198     def savePageInfo(self, pageURL):
199         logging.info(u"準備獲取網頁內容(超時設置:60秒):" + pageURL)
200         contents = self.getContents(pageURL, 60)
201         logging.info(u"網頁內容獲取完成,子路徑個數:" + str(len(contents)))
202         index = 1
203         for item in contents:
204             #(1)item[0]子路徑URL, item[1]子路徑名稱
205             folderURL = item[0]
206             folderName = self.contentFolder + '\\' + str(index) + "-" + self.getPathName(item[1])
207             self.createFolder(folderName)
208             index = index + 1
209             
210             #(2)判斷連接頭部合法性和重複性
211             if not folderURL.startswith('http://'):
212                 folderURL = pageURL + folderURL
213             if folderURL in self.urlDB:
214                 logging.info(u'"' + folderName + u'"的連接地址(已訪問,跳過)爲:' + folderURL)
215                 continue
216             else:
217                 logging.info(u'"' + folderName + u'"的連接地址爲:' + folderURL)
218             
219             #(3)獲取圖片URL列表,成功則保存圖片
220             images, succeed = self.getAllImgURL(folderURL)
221             if succeed:
222                 succeed = self.saveImgs(images, folderName)
223                 if succeed:
224                     self.urlDB.add(folderURL)
225                     self.writeToDatabase(folderURL)    
226 
227 # 初始化系統日誌存儲
228 InitLogger() 
229 # 傳入初始網頁地址,自動啓動爬取圖片:
230 spider = Spider()
231 spider.loadDatabase()
232 spider.savePageInfo('http://365.tw6000.com/xtu/')
233 logging.info(u"所有網頁內容爬取完成!程序退出。")
查看所有代碼

2、問題歷史

  在上面的代碼中有很多的細節是優化解決過的,相關的知識點以下:

2.1 日誌系統

  Python的日誌系統是至關的不錯,很是的方便,詳細的資料能夠參考Python官方文檔,或者上一篇博文也是提到過的:《Python中的日誌管理Logging模塊》,應用到我這個爬蟲這裏的代碼就是myLogger.py模塊了,用起來很方便:

 1 __author__ = 'KLH'
 2 # -*- coding:utf-8 -*-
 3 
 4 import logging
 5 import time
 6 
 7 def InitLogger():
 8     logFileName = 'log_' + time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())) + '.txt'
 9     logging.basicConfig(level=logging.DEBUG,
10                         format='[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s] - %(message)s',
11                         filename=logFileName,
12                         filemode='w')
13     
14     # 定義一個StreamHandler將INFO級別以上的信息打印到控制檯
15     console = logging.StreamHandler()
16     console.setLevel(logging.INFO)
17     formatter = logging.Formatter('[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s] - %(message)s')
18     console.setFormatter(formatter)
19     logging.getLogger('').addHandler(console)
查看myLogger.py

  注意在爬蟲的代碼執行前調用一下該函數:

from myLogger import *
#
初始化系統日誌存儲 InitLogger()

  日誌調用和打印的結果以下:

logging.info(u"準備獲取網頁內容(超時設置:60秒):" + pageURL)

#打印結果以下:
[2015-11-09 22:35:02,976][spider2.py:182][INFO] - 準備獲取網頁內容(超時設置:60秒):http://365.XXXXX.com/xtu/

2.2 記錄訪問歷史

  這裏記錄URL的訪問歷史是爲了防止執行重複任務,在內存中保持一個Set就能知足需求,在磁盤上能夠簡單的保存成一個TXT文件,每一個URL保存成一行便可。因此這裏是邏輯順序應該是在初始化時建立一個Set用來保存訪問歷史,任務執行以前從數據庫中加載訪問歷史,若是是首次運行還沒有建立數據庫還須要進行一次建立操做。而後就好辦了,每次完成一個URL的訪問就保存一下訪問記錄。相關代碼以下:

 1 # 類初始化
 2 def __init__(self):
 3     self.contentFolder = u"抓取內容"
 4     self.dbName = "url.db"              # 一、定義數據庫文件名
 5     self.createFolder(self.contentFolder)   # 二、建立內容存儲目錄  6     self.urlDB = set()                # 三、建立內存數據庫  7 
 8 # 獲取URL數據庫以獲取爬過的網頁地址
 9 def loadDatabase(self):
10     isExists = os.path.exists(self.dbName)  # 四、首先判斷是不是首次運行,若是數據庫文件不存在則建立一下 11     if not isExists:
12         logging.info(u"建立URL數據庫文件:'" + self.dbName + u"'")
13         f = open(self.dbName, 'w+')
14         f.write("#Create time: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + '\n')
15         f.close()
16         return
17     db = open(self.dbName, 'r')         # 五、從磁盤中加載數據庫 18     for line in db.readlines():
19         if not line.startswith('#'):
20             self.urlDB.add(line.strip('\n'))
21     db.close()
22     logging.info(u"URL數據庫加載完成!")
23 
24 # 追加數據庫文件
25 def writeToDatabase(self, url):         # 六、在系統運行過程當中,如需記錄日誌,追加日誌內容便可 26     db = open(self.dbName, 'a')
27     db.write(url + '\n')
28     db.close()

  有了上面的代碼,在記錄日誌過程當中就很方便了:

succeed = self.saveImgs(images, folderName)
    if succeed:
        self.urlDB.add(folderURL)
        self.writeToDatabase(folderURL)

2.3 異常處理

  訪問網絡資源不可避免的會有不少異常狀況,要處理這些異常狀況才能穩定運行,Python的異常處理很簡單,請參考以下獲取網頁的代碼段:

 1 # 獲取索引頁面的內容
 2 def getPage(self, pageURL, second):
 3     try:
 4         request = urllib2.Request(pageURL)
 5         response = urllib2.urlopen(request, timeout = second)
 6         data = response.read()
 7         return data.decode('gbk'), True
 8     except urllib2.HTTPError,e:    # HTTPError必須排在URLError的前面
 9         logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read())
10         return "", False
11     except urllib2.URLError, e:
12         logging.error("URLError reason:" + str(e.reason) + " - " + str(e))
13         return "", False
14     except Exception, e:       # 其餘全部類型的異常 15         logging.error(u"獲取網頁失敗:" + str(e))
16         return "", False       # 這裏返回兩個值方便判斷

2.4 網頁自動編碼判斷

  昨天在爬取網頁的過程當中忽然發現,有的頁面竟然說編碼不能經過utf-8進行解析,我看了看有的網頁確實不是utf-8編碼的,那怎麼辦呢?怎麼才能自動進行解碼?從網上能夠搜索到一個Python的開源庫很好用,叫作chardet,默認Python2.7是不帶的須要下載,好比我下載的是:chardet-2.3.0.tar.gz

  有了這個壓縮包解壓出來cahrdet子文件夾拷貝到:C:\Python27\Lib\site-packages目錄下便可。下面看看用法實例:

 1 import chardet
 2 
 3 # 獲取頁面全部圖片
 4 def getAllImgURL(self, infoURL):
 5   images = []
 6   succeed = True
 7   try:
 8     data = urllib2.urlopen(infoURL).read()  # 一、先獲取網頁內容
 9     chardet1 = chardet.detect(data)        # 二、再調用該模塊的方法自動判斷網頁編碼
10     page = data.decode(str(chardet1['encoding']))   # 三、注意,獲得的chardet1是一個字典相似於:{'confidence': 0.98999999999999999, 'encoding': 'GB2312'}
11     
12     # 第一種解碼格式:
13     pattern = re.compile('<option value="(.*?)">(.*?)</option>')
14     items = re.findall(pattern, page)
15     # item[0]爲圖片URL尾部,item[1]爲圖片名稱
16     for item in items:
17       imageURL = infoURL + item[0]
18       if imageURL in self.urlDB:
19         logging.info(u"得到圖片URL(曾被訪問,跳過):" + imageURL)
20       else:
21         logging.info(u"得到圖片URL:" + imageURL)
22         images.append(imageURL) 
23             
24     # 第二種解碼格式
25     pattern = re.compile('<IMG src="(.*?)".*?>')
26     items = re.findall(pattern, page)
27     # item爲圖片URL
28     for item in items:
29       if item.startswith('http://'):    # 四、這裏也注意一下,在這種網頁中相對路徑的圖片都是插圖之類的小圖片不須要下載,因此過濾掉了。
30         if item in self.urlDB:
31             logging.info(u"得到圖片URL(曾被訪問,跳過):" + item)
32         else:
33           logging.info(u"得到圖片URL:" + item)
34           images.append(item)             
35           
36   except Exception, e:
37     logging.warning(u"在獲取子路徑圖片列表時出現異常:" + str(e))
38     succeed = False
39   return images, succeed

2.5 遠程主機重置連接(Errno 10054)

  在今天的爬取過程當中我發現了一個問題,爬到後面的內容都出錯了,錯誤信息參見以下:

[2015-11-09 23:48:51,082][spider2.py:130][INFO] - 開始準備保存圖片(超時設置:300秒):http://xz1.XXXX.com/st/st-06/images/009.jpg
[2015-11-09 23:48:55,095][spider2.py:160][ERROR] - 保存圖片失敗:[Errno 10054] 
[2015-11-09 23:48:55,096][spider2.py:140][WARNING] - 保存圖片失敗(耗時:4.01399993896秒):http://xz1.XXXX.com/st/st-06/images/009.jpg
[2015-11-09 23:48:55,098][spider2.py:130][INFO] - 開始準備保存圖片(超時設置:300秒):http://xz1.XXXX.com/st/st-06/images/010.jpg
[2015-11-09 23:48:56,576][spider2.py:160][ERROR] - 保存圖片失敗:[Errno 10054] 
[2015-11-09 23:48:56,578][spider2.py:140][WARNING] - 保存圖片失敗(耗時:1.48000001907秒):http://xz1.XXXX.com/st/st-06/images/010.jpg

  能夠看到都在報這個錯誤代碼,網上一查,發現有不少同窗已經遇到過了,請參考知乎上的討論:http://www.zhihu.com/question/27248551

  從網上討論的結果來看,能夠確定的是直接緣由在於「遠程主機主動關閉了當前連接」,而根本緣由則在於:網站啓用了反爬蟲的策略,也就是說個人爬蟲爬的過快而且被發現不是真正的瀏覽器瀏覽了。怎麼辦了?針對這兩個緣由逐個處理,一是假裝成瀏覽器,二是不要訪問的過快,三是每次訪問完成都關閉連接。相關代碼以下:

 1 # 傳入圖片地址,文件名,超時時間,保存單張圖片
 2 def saveImg(self, imageURL, fileName, second):
 3   try:            
 4     headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'}  # 一、這裏構造一個瀏覽器的頭部  5     request = urllib2.Request(imageURL, headers = headers)
 6     u = urllib2.urlopen(request, timeout = second)     # 二、這裏的超時設置timeout不要太長  7     data = u.read()
 8     f = open(fileName, 'wb')
 9     f.write(data)
10     f.close()
11     u.close()    # 三、注意這裏的連接要主動調用一下關閉 12     return True
13   except urllib2.HTTPError,e:    #HTTPError必須排在URLError的前面
14     logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read())
15     return False
16   except urllib2.URLError, e:
17     logging.error("URLError reason:" + str(e.reason) + " - " + str(e))
18     return False
19   except Exception, e:
20     logging.error(u"保存圖片失敗:" + str(e))
21     return False  # 四、注意調用完這個函數以後再time.sleep(1)一下,防止過快訪問被發現了,呵呵

 2.6 使用BeautifulSoup來分析網頁

   使用正則表達式來匹配網頁中的字段確實是太費勁了。用BeautifulSoup就省力多了,功能很強大:

soup = BeautifulSoup(page_data, 'lxml')
entries = soup.find_all(src=re.compile('.jpg'), border='0')    # 找到全部字段爲src正則匹配的標籤內容,同時增長一個約束條件是border=‘0’

   Beautiful Soup支持Python標準庫中的HTML解析器,還支持一些第三方的解析器,若是咱們不安裝它,則 Python 會使用 Python默認的解析器,lxml 解析器更增強大,速度更快,推薦安裝。

相關文章
相關標籤/搜索