我使用的網絡爬蟲下載網頁的算法是廣度搜索(BFS),網絡上對爬蟲實現算法的評價中,廣度搜索的算法是排行第二的,最好的算法是按網頁重要性排序後再肯定下載順序(這個算法很靈活,怎麼排序本人不是很瞭解)。
進入正題,描述如何實現:
拿到一個已經有了描述的辦法,實現它能夠按自頂向下的思路,先將大的步驟描述出來,而後分割成小的問題,一部分一部分地解決。
對於一個網絡爬蟲,若是要按廣度遍歷的方式下載,它就是這樣幹活的:
1.從給定的入口網址把第一個網頁下載下來
2.從第一個網頁中提取出全部新的網頁地址,放入下載列表中
3.按下載列表中的地址,下載全部新的網頁
4.從全部新的網頁中找出沒有下載過的網頁地址,更新下載列表
5.重複三、4兩步,直到更新後的下載列表爲空表時中止
其實就是簡化成下面的步驟:
1.按下載列表進行下載
2.更新下載列表
3.循環操做1,2,直到列表爲空結束
因此最初的設想就是寫一個函數裏面幹這個:
def craw():
while len(urlList) != 0
Init_url_list()
Download_list()
Update_list()
固然,上面這個函數是工做不起來的,它只是最頂層的一個想法,底層的實現還沒作。不過這一步很重要,至少讓本身知道該幹什麼了。
下面的事情就是將函數每一部分實現,這個能夠放在一個類裏去實現,我把它命名爲WebCrawler。
在python裏,要按一個地址下載一個網頁那並非什麼難事,你能夠用urllib裏的urlopen去鏈接上某一個網頁,而後調用獲取到的對象的read方法,能夠獲得網頁的內容的字符串,像這樣:
IDLE 2.6.6 ==== No Subprocess ====
>>> import urllib
>>> f = urllib.urlopen('http://www.hfut.edu.cn')
>>> s = f.read()
>>>
這樣上面變量 s 裏面存的就是從http://www.hfut.edu.cn這個地址裏獲取到的網頁的內容了,是str數據類型。下面你要怎麼用均可以了,把寫入文件或從中提取新的地址就隨你意了。固然,只要寫入文件,就算下載完了這個頁面。
一個爬蟲程序下載的速度確定是很重要的問題,誰也不想用一個單線程的爬蟲用一次只下一個網頁速度去下載,我在學校校園網,測試了單線程的爬蟲,平均每秒才 下1k。因此解決的辦法只有用多線程,多開幾個鏈接同時下載就快了。本人是Python新手,東西都是臨時拿來用的。
下載線程我是用了另一個類,命名爲CrawlerThread,它繼承了threading.Thread這個類。
由於涉及到更新下載列表的問題,線程對某個表的讀寫還要考慮同步,我在代碼裏使用了線程鎖,這個用threading.Lock()構造對象。調用對象的 acquire()和release()保證每次只有一個線程對錶進行操做。固然,爲了保證表的更新可以實現,我使用了多個表,一個表確定辦不成。由於你 即要知道當前要下載的網絡地址,還要知道你已經下載過的網絡地址。你要把已經下載過的地址重新的網頁中獲取到的網址列表中除去,這當中又涉及了一些臨時的 表。
爬蟲在下載網頁的時候,最好還要把哪一個網頁存到了哪一個文件記錄好,而且記錄好網頁是搜索到廣度搜索到的第幾層的深度記錄好,由於若是要作搜索引擎,這個都 是對製做索引和對網頁排序有參考價值的信息。至少你本身會想知道爬蟲給你下載到了什麼,都放在哪了吧。對應的寫記錄的語句我在代碼裏的行末用##標註出來 了。
寫的文字已經不少了,不想再寫了,直接貼上代碼:
文件Test.py內容以下:(它調用了WebCrawler,運行時是運行它)
--------------------------------------------------------
# -*- coding: cp936 -*-
import WebCrawler
url = raw_input('設置入口url(例-->http://www.baidu.com): \n')
thNumber = int(raw_input('設置線程數:')) #以前類型未轉換出bug
wc = WebCrawler.WebCrawler(thNumber)
wc.Craw(url)
文件WebCrawler.py內容以下:
--------------------------------------------------------
# -*- coding: cp936 -*-
import threading
import GetUrl
import urllib
g_mutex = threading.Lock()
g_pages = [] #線程下載頁面後,將頁面內容添加到這個list中
g_dledUrl = [] #全部下載過的url
g_toDlUrl = [] #當前要下載的url
g_failedUrl = [] #下載失敗的url
g_totalcount = 0 #下載過的頁面數
class WebCrawler:
def __init__(self,threadNumber):
self.threadNumber = threadNumber
self.threadPool = []
self.logfile = file('#log.txt','w') ##
def download(self, url, fileName):
Cth = CrawlerThread(url, fileName)
self.threadPool.append(Cth)
Cth.start()
def downloadAll(self):
global g_toDlUrl
global g_totalcount
i = 0
while i < len(g_toDlUrl):
j = 0
while j < self.threadNumber and i + j < len(g_toDlUrl):
g_totalcount += 1 #進入循環則下載頁面數加1
self.download(g_toDlUrl[i+j],str(g_totalcount)+'.htm')
print 'Thread started:',i+j,'--File number = ',g_totalcount
j += 1
i += j
for th in self.threadPool:
th.join(30) #等待線程結束,30秒超時
self.threadPool = [] #清空線程池
g_toDlUrl = [] #清空列表
def updateToDl(self):
global g_toDlUrl
global g_dledUrl
newUrlList = []
for s in g_pages:
newUrlList += GetUrl.GetUrl(s) #######GetUrl要具體實現
g_toDlUrl = list(set(newUrlList) - set(g_dledUrl)) #提示unhashable
def Craw(self,entryUrl): #這是一個深度搜索,到g_toDlUrl爲空時結束
g_toDlUrl.append(entryUrl)
depth = 0
while len(g_toDlUrl) != 0:
depth += 1
print 'Searching depth ',depth,'...\n\n'
self.downloadAll()
self.updateToDl()
content = '\n>>>Depth ' + str(depth)+':\n' ##(該標記表示此語句用於寫文件記錄)
self.logfile.write(content) ##
i = 0 ##
while i < len(g_toDlUrl): ##
content = str(g_totalcount + i) + '->' + g_toDlUrl[i] + '\n' ##
self.logfile.write(content) ##
i += 1 ##
class CrawlerThread(threading.Thread):
def __init__(self, url, fileName):
threading.Thread.__init__(self)
self.url = url #本線程下載的url
self.fileName = fileName
def run(self): #線程工做-->下載html頁面
global g_mutex
global g_failedUrl
global g_dledUrl
try:
f = urllib.urlopen(self.url)
s = f.read()
fout = file(self.fileName, 'w')
fout.write(s)
fout.close()
except:
g_mutex.acquire() #線程鎖-->鎖上
g_dledUrl.append(self.url)
g_failedUrl.append(self.url)
g_mutex.release() #線程鎖-->釋放
print 'Failed downloading and saving',self.url
return None #記着返回!
g_mutex.acquire() #線程鎖-->鎖上
g_pages.append(s)
g_dledUrl.append(self.url)
g_mutex.release() #線程鎖-->釋放
文件GetUrl.py內容以下:(它裏面的GetUrl從一個存有網頁內容的字符串中獲取全部url並以一個list返回,這部分實現方法不少,你們能夠本身寫個更好的) -------------------------------------------------------- urlSep = ['<','>','\\','(',')', r'"', ' ', '\t', '\n'] urlTag = ['http://'] def is_sep(ch): for c in urlSep: if c == ch: return True return False def find_first_sep(i,s): while i < len(s): if is_sep(s[i]): return i i+=1 return len(s) def GetUrl(strPage): rtList = [] for tag in urlTag: i = 0 i = strPage.find(tag, i, len(strPage)) while i != -1: begin = i end = find_first_sep(begin+len(tag),strPage) rtList.append(strPage[begin:end]) i = strPage.find(tag, end, len(strPage)) return rtList