很多時候我們想要獲得網站的數據,但是網站并沒有提供相應的API調用,這時候應該怎么辦呢?還有的時候我們需要模擬人的一些行為,例如點擊網頁上的按鈕等,又有什么好的解決方法嗎?這些正是python和網頁爬蟲的應用場景。python是一種動態解釋性語言,簡單的語法和強大的庫支持使得python在數據收集、數據分析、網頁分析、科學計算等多個領域被廣泛使用。
本文主要總結一下如何用python自己寫一個簡單的爬蟲,以及可能出現的問題與解決方法。
首先介紹一下大概的思路,首先需要在程序中連接網站并發送GET請求得到html文件,然后需要解析html文件,根據某種規律將需要的信息提取出來,然后用合適的方式處理數據并保存。
(一)python中用于http連接的庫——urllib2
首先放上python文檔的鏈接,英文好的同學可自由閱讀:https://docs.python.org/2/library/urllib2.html?highlight=urllib2#module-urllib2
這個庫是python用來解析URL主要是HTTP的主要工具,而且提供了對身份認證、重定向、cookie等的支持,具體用法可以仔細的讀文檔,這里作為最簡單的場景,我介紹如何打開一個URL并得到對應的HTML頁面。只要使用一個函數:
urllib2.urlopen(url[, data[, timeout[, cafile[, capath[, cadefault[, context]]]]])
1、函數的參數url可以是一個string也可以是一個request對象,如果提供了參數列表中data的值(dict類型)那么函數將發送POST請求,默認情況是發送GET。另外,很多人說沒法設置連接的timeout,然后提供了一堆類似修改socket全局timeout的方法,其實這里可以直接設定的嘛!
2、函數的返回值。函數返回一個類文件的東西,which means,函數返回的值可以當一個文件類來操作,只是多了三個方法:
3、函數拋出的異常:URLError例如發生無網絡連接這種事情。socket.timeout如果你設置了timeout參數,超時后便會拋出此異常。
所以這里的代碼可以這樣寫:
1 import urllib2 2 import socket 3 def gethtml(url): 4 try: 5 f = urllib2.urlopen(url,timeout=10) 6 data = f.read() 7 except socket.timeout, e: 8 data = None 9 PRint "time out!"10 with open("timeout",'a') as log:11 log.write(url+'/n')12 except urllib2.URLError,ee:13 data = None14 print "url error"15 finally:16 return data
這樣就可以得到對應URL的網頁的html代碼了(但是在具體應用你可能會碰到蛋疼的問題,我碰到的會列在下面)
(二)對html的解析
獲得了網頁源代碼后我們需要從里面提取出接下來要爬取的URL或者我們需要的數據,這就需要對html解析。2.7版本的python中,內置的解析類是HTMLParser,庫文檔:https://docs.python.org/2/library/htmlparser.html?highlight=htmlparser
其實,你要做的全部,就是繼承這個類,然后把里面的接口函數實現,他看起來是這樣子的:
1 from HTMLParser import HTMLParser 2 3 # create a subclass and override the handler methods 4 class MyHTMLParser(HTMLParser): 5 def handle_starttag(self, tag, attrs): 6 print "Encountered a start tag:", tag 7 def handle_endtag(self, tag): 8 print "Encountered an end tag :", tag 9 def handle_data(self, data):10 print "Encountered some data :", data
例如對于一個最簡單的html文件:
1 <html>2 <head>3 <title>Test</title>4 </head>5 <body>6 <h1>Parse me!</h1>7 </body>8 </html>
當程序遇到每一個標簽的開始時便會調用handle_starttag函數,遇到標簽結束就會調用handle_endtag,遇到標簽之間的數據就會調用handle_data可以結合最后貼出的示例代碼進一步理解。想要解析這個文件只要如下寫:
#例如借用上面的gethtml函數def parse(): html = gethtml("http://baidu.com",timeout=10) parser = MyParser() parser.feed(html) parser.close()
調用feed函數把html放入緩沖區,調用close函數強制parser開始解析html
(三)問題與解決方法:
1、得到的html文檔是亂碼
這個問題可能有很多原因引起,最主要的兩個是:網頁的編碼方式和你的解碼方式不匹配。關于編碼和解碼推薦讀一下這個博客:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819196283586a37629844456ca7e5a7faa9b94ee8000
這時候你會明白編碼問題確實導致了文件的不可讀。所以你要正確獲得網頁的編碼方式,然后調用對應的解碼器,代碼可能是這樣的:
f = urllib2.urlopen(url,timeout=10)data = f.read() # decode the htmlcontentType = f.headers.get('Content-Type')if contentType.find("gbk"): data = unicode(data, "GBK").encode("utf-8")elif contentType.find("utf-8"): pass
當然,編碼方式可能多種,不止GBK
2、還是亂碼
如果還是亂碼,可能你依然沒有選對解碼器,也可能是網頁被壓縮過了,這時候你要先把網頁解壓縮,然后正確解碼,解碼的代碼可能是這樣的:
1 import StringIO, gzip 2 3 f = urllib2.urlopen(url,timeout=10) 4 # consider some html is compressed by server with gzip 5 isGzip = f.headers.get('Content-Encoding') 6 if isGzip: 7 compressedData = f.read() 8 compressedStream = StringIO.StringIO(compressedData) 9 gzipper = gzip.GzipFile(fileobj=compressedStream)10 data = gzipper.read()11 else:12 data = f.read()
3、調用urlopen函數后程序卡住
這個的主要問題是沒有設置timeout參數,導致網絡不通暢時引起urlopen函數無限等待,程序卡死,所以,設置timeout=10例如,單位是秒,并處理拋出的socket.timeout異常,就可以避免這個問題了。
4、被服務器拉黑
如果你對某個域名訪問速度太快,就可能被服務器認定為潛在的DDos攻擊,IP就會被封一段時間。解決方法是不要過快的訪問可以使用sleep語句參數單位為秒。
import timefor url in urllist: f = urllib2.urlopen(url,timeout=10) ... time.sleep(1)
(四)小爬蟲
自己寫了一個小爬蟲,目的是爬取www.gaokao.com網上所有本科大學歷年的分數線等數據,其中schoollist文件存儲了所有大學的ID號,你可以手寫一個list代替例如:
schoollist = ['1','2','3']
代碼就暴力貼了:
#!/usr/bin/env python# -*- coding: utf-8 -*-__author__ = 'holmes'from HTMLParser import HTMLParserimport urllib2import StringIO, gzipimport threadingimport osimport timeimport sysimport socket# hard codeLOCATION = ("北京", "天津", "遼寧", "吉林", "黑龍江", "上海", "江蘇", "浙江", "安徽", "福建", "山東", "湖北", "湖南", "廣東", "重慶", "四川", "陜西", "甘肅", "河北", "山西", "內蒙古", "河南", "海南", "廣西", "貴州", "云南", "西藏", "青海", "寧夏", "新疆", "江西",)# hard codeSUBJECT = ("理科", "文科",)'''Rules for URLhttp://college.gaokao.com/school/tinfo/%d/result/%d/%d/ %(schoolID,localID,subID)where localID from 1 to 31where subID from 1 to 2'''SEED = "http://college.gaokao.com/school/tinfo/%s/result/%s/%s/"SID = "schoolID" # file name contains school IDsclass SpiderParser(HTMLParser): def __init__(self, subject=1, location=1): HTMLParser.__init__(self) self.campus_name = "" self.subject = SUBJECT[subject - 1] self.location = LOCATION[location - 1] self.table_content = [[], ] self.line_no = 0 self.__in_h2 = False self.__in_table = False self.__in_td = False def handle_starttag(self, tag, attrs): if tag == "h2": self.__in_h2 = True if tag == "table": self.__in_table = True if tag == "tr" and len(attrs) != 0: if self.__in_table: self.table_content[self.line_no].append(self.campus_name) self.table_content[self.line_no].append(self.subject) self.table_content[self.line_no].append(self.location) if tag == "td": if self.__in_table: self.__in_td = True def handle_endtag(self, tag): if tag == "h2": self.__in_h2 = False if tag == "table": self.__in_table = False if tag == "tr": if self.__in_table: self.line_no += 1 self.table_content.append([]) if tag == "td": if self.__in_table: self.__in_td = False def handle_data(self, data): if self.__in_h2: self.campus_name = data if self.__in_td: self.table_content[self.line_no].append(data)def getschoolID(): with open(SID, mode='r') as rf: idlist = rf.readlines() print idlist return idlistdef gethtml(url): try: f = urllib2.urlopen(url,timeout=10) # consider some html is compressed by server with gzip isGzip = f.headers.get('Content-Encoding') if isGzip: compressedData = f.read() compressedStream = StringIO.StringIO(compressedData) gzipper = gzip.GzipFile(fileobj=compressedStream) data = gzipper.read() else: data = f.read() # decode the html contentType = f.headers.get('Content-Type') if contentType.find("gbk"): data = unicode(data, "GBK").encode("utf-8") elif contentType.find("utf-8"): pass except socket.timeout, e: data = None print "time out!" with open("timeout",'a') as log: log.write(url+'/n') finally: return datadef parseandwrite((slID, lID, sID), wfile): try: url = SEED % (slID, lID, sID) html = gethtml(url) if html is None: print "pass a timeout" return parser = SpiderParser(sID, lID) parser.feed(html) parser.close() if parser.line_no != 0: for line in parser.table_content: for item in line: if "--" in item: item = "NULL" wfile.write(item + ',') wfile.write('/n') except urllib2.URLError, e: print "url error in parseandwrite()" raisedef thread_task(idlist, name): try: print "thread %s is start" % name wf = open(name, mode='w') wf.write("大學名稱,文理,省份,年份,最低分,最高分,平均分,錄取人數,錄取批次") for sID in idlist: print name + ":%s" % idlist.index(sID) sID = sID.strip('/n') i = 1.0 for localID in range(1, 32): for subID in range(1, 3): parseandwrite((sID, localID, subID), wf) sys.stdout.write("/rprocess:%.2f%%" % (i / 62.0 * 100)) sys.stdout.flush() i += 1.0 time.sleep(1) except urllib2.URLError: with open("errorlog_" + name, 'w') as f: f.write("schoolID is %s , locationID is %s ,subID is %s" % (sID, localID, subID)) print "schoolID is %s ,locationID is %s ,subID is %s" % (sID, localID, subID) finally: wf.close()THREAD_NO = 1def master(): school = getschoolID() for i in range(THREAD_NO): path = os.path.join(os.path.curdir, "errorlog_" + str(i)) if os.path.exists(path): with open("errorlog_" + str(i), 'r') as rf: sID = rf.readline().split()[2] start = school.index(sID) else: start = len(school) / THREAD_NO * i end = len(school) / THREAD_NO * (i + 1) - 1 if i == THREAD_NO - 1: end = len(school) - 1 t = threading.Thread(target=thread_task, args=(school[start:end], "thread" + str(i),)) t.start() print "start:%s /n end:%s" % (start, end) t.join() print "finish"if __name__ == '__main__': # thread_task(["1"],"test") master() # gethtml("http://www.baidu.com")
新聞熱點
疑難解答