1 #!/usr/bin/python 使用魔法字符調(diào)用python
2
3 from sys import argv ?導(dǎo)入sys是導(dǎo)入python解釋器和他環(huán)境相關(guān)的參數(shù)
4 from os import makedirs,unlink,sep
os主要提供對系統(tǒng)路徑判耕,文件重命名和刪除文件所需的函數(shù)
makedirs是創(chuàng)建遞歸文件夾的函數(shù)。比如說我們要創(chuàng)建一個新的目錄,/python/HTML/crawl,但是目前這三個文件夾都不存在勃教,如果使用mkdir命令的話需要使用三次才能完成女气,但是使用os.makedir只需使用一次就可以創(chuàng)建好整個目錄坝咐。
os.makedirs(os.path.join(os.erviron["HOME"],"python","HTML","crawl")
os.unlink(path)刪除file路徑解虱,和remove()相同盆色。
sep os.sep系統(tǒng)用此來分割路徑名
5 from os.path import dirname,exists,isdir,splitext
使用os中的這些模塊來提取dirname路徑名粥帚,exists,isdir是文件類型測試胰耗,測試是否是一個目錄,splitext是將文件名和文件后綴分離芒涡。分成目錄文件名和后綴兩部分柴灯。
6 from string import replace,find,lower
導(dǎo)入string模塊,用于字符串的替換费尽,查找赠群,和小寫化。
7 from htmllib import HTMLParser
8 from urllib import urlretrieve
urlretrieve()函數(shù)用于將HTML文件整個下載到你的本地硬盤中去旱幼。
9 from urlparse import urlparse,urljoin
urlparse用于將URL分解成6個元素
而urljoin用于將baseurl和newurl組合在一起
10 from formatter import DumbWriter,AbstractFormatter
formatter函數(shù)主要用于格式化文本
11 from cStringIO import StringIO
調(diào)用cStringIO函數(shù)對內(nèi)存中的文件進(jìn)行處理
12
13 class Retriever:
Retriever類負(fù)責(zé)從網(wǎng)上下載網(wǎng)頁并對每一個文檔里面的連接進(jìn)行分析查描,如果符合下載原則就添加到“待處理”隊(duì)列中。從網(wǎng)上下載到的每個主頁都有一個與之對應(yīng)的Retriever實(shí)例速警。Retriever有幾個幫助實(shí)現(xiàn)功能的方法叹誉,分別是:構(gòu)造器(__init__()),filename(),download()和parseAndGetLinks()。
14 ?def __init__(self,url): 定義構(gòu)造器闷旧,指向當(dāng)前類的當(dāng)前實(shí)例的引用长豁。 ? self 指向新創(chuàng)建的
對象,另外一個參數(shù)是url.構(gòu)造器實(shí)例化一個Retriever對象忙灼,并且把URL字符串和從filename()返回的與之對應(yīng)的文件名保存為本地屬性匠襟。
15 ? self.url=url
將url的值付給self.url
16 ? self.file=self.filename(url)
???
17 ?def filename(self,url,deffile="index.html"):
定義filename方法,涉及另外兩個參數(shù)该园,url,deffile,很明顯deffile是后綴
18 ? parsedurl=urlparse(url,"http:",0)
urlparse(urlstr,defProtsch=None,allowFrag=None),defProtsch定義了缺醒的網(wǎng)絡(luò)協(xié)議和下載方式酸舍,allow是一個表示是否允許在URL中使用不完整成分的操作標(biāo)志。allow_fragment如果是false,即使在URL addressing scheme支持fragment identifiers得情況下fragment identifiers也不允許里初,默認(rèn)情況下fragment的默認(rèn)值是true.
19 ? path=parsedurl[1]+parsedurl[2]
從urlparse分離出來的六個元素分別是(prot_shc,net_loc,path,params,query,frag).
parseurl[1]是net_loc,parseurl[2]是path.
和在一起正好是整個路徑
20 ? ext=splitext(path)
將path分解成目錄文件名和后綴標(biāo)志啃勉。
21 ? if ext[1]=="":
如果沒有文件。ext是一個字符串双妨,ext[0]就是目錄文件名淮阐,而ext[1]就是后綴名叮阅,說明沒有后綴
22 ? ?if path[-1]=="/":
并且path是比如說是以我的博客為例,http://blog.csdn.net/yangwenchao1983泣特,分離后path[-1]=3,也就是字符串的最后一個字母浩姥,如果是/,說明有文件內(nèi)容状您,
23 ? ? path=path+deffile
如果URL沒有尾綴的文件名勒叠,就用缺性的"index.html“作為文假名,可以說是一個主王爺膏孟,上面有各種文件公下載眯分,現(xiàn)在沒有合適的文件,我們酒吧index.html作為補(bǔ)充骆莹。
24 ? else:
25 ? ?path=path+"/"+deffile
如果是一個完整的文件名颗搂,我們需要在后面加上/index.html
如果不含有"/"符號的話,
26 ? dir=dirname(path)
提取path字符串的目錄名稱
27 ? if sep!="/":
如果文件的分割符不是/
28 ? ?dir=replace(dir,"/",sep)
將dir中的/替換成分割符幕垦,/
29 ? if not isdir(dir):
使用isdir辨別文件類型不是目錄。
30 ? ?if exists(dir): unlink(dir)
如果不是目錄文件傅联,就是用unlink移除先改,
31 ? ?makedirs(dir)
重新使用makedirs創(chuàng)建目錄文件
32 ? return path
返回經(jīng)過整理的路徑
33 ?def download(self):
定義download()方法,使用try...except...來進(jìn)行異常處理蒸走,
34 ? try:
35 ? ?retval=urlretrieve(self.url,self.file)
urlretrieve()不像urlopen()那樣對URL進(jìn)行讀操作仇奶,它只是簡單的把位于urlstr處的HTML文件整個下載到你的本地硬盤中去,如果沒有給出localfile,它就會把數(shù)據(jù)保存到一個臨時(shí)文件中去比驻。很明顯该溯,這行程序的意思就是將self.url從望上的某個地方拷貝到硬盤的self.file中去。
36 ? except IOError:
如果文件不存在别惦,就會引發(fā)IOerror,
37 ? ?retval=("***ERROR: invalid URL "%s"" %\self.url,)
沒有在有效的網(wǎng)址上找到這個文件狈茉,就將"***ERROR: invalid URL "%s""打印出來
38 ? return retval
返回得到的文件
39 ?def parseAndGetLinks(self):
如果上面的的處理沒有發(fā)現(xiàn)任何錯誤,就會調(diào)用parseAndGetLinks()對新下載打破的主頁進(jìn)行分析掸掸,確定對那個主頁上的每一個連接應(yīng)該采取什么樣的行動氯庆。
40 ? self.parser=HTMLParser(AbstractFormatter(DumbWriter(StringIO())))
使用HTMLParser的方法進(jìn)行處理,StringIO是從內(nèi)存中讀取數(shù)據(jù),DumbWriter將事件流轉(zhuǎn)換為存文本文檔扰付。
41 ? self.parser.feed(open(self.file).read())
將self.file文件打開并且一次性讀入上面定義的的文件中去
42 ? self.parser.close()
關(guān)閉文件
43 ? return self.parser.anchorlist
返回地址和日期
44
45 class Crawler:
Crawler由三個數(shù)據(jù)項(xiàng)組成堤撵,這三個數(shù)據(jù)項(xiàng)是由構(gòu)造器在實(shí)例化階段報(bào)存在這里的。
46 ?count = 0
靜態(tài)下載主頁計(jì)數(shù)器
47 ?def __init__(self,url):
48 ? self.q=[url]
第一個數(shù)據(jù)是q,這是一個有下載連接組成的隊(duì)列羽莺,這個清單在執(zhí)行過程中是會變化的实昨,沒處理一個主頁它就縮短一次,而在各下載主頁中發(fā)現(xiàn)一個新的連接就會被加長盐固。
49 ? self.seen=[]
Crawler的另外兩個數(shù)據(jù)項(xiàng)包括seen-這是我們已經(jīng)下載過的全體連接所組成的一個列表荒给;
50 ? self.dom=urlparse(url)[1]
把主連接的域名報(bào)存在dom中丈挟,用這個值核對后續(xù)連接是否屬于這同一個區(qū)域。
51 ?def getPage(self,url):
getPage()方法用第一個連接實(shí)例化出一個Retriever對象锐墙,從她開始進(jìn)行后續(xù)的處理礁哄。
52 ? r=Retriever(url)
使用上面定義過得Retriever類,付給r溪北。
53 ? retval=r.download()
下載網(wǎng)頁連接桐绒,
54 ? if retval[0]=="*":
55 ? ?print retval,"...skipping parse"
56 ? ?return
57 ? Crawler.count=Crawler.count+1
Crawler還有一個靜態(tài)數(shù)據(jù)叫做count。這個計(jì)數(shù)器的作用就是記錄我們呢已經(jīng)從望紅色那個下載到的對象的個數(shù)之拨,每成功下載一個主頁茉继,就讓它增加一個數(shù)。
58 ? print "\n(",Crawler.count,")"
59 ? print "URL:",url
60 ? print "FILE:",retval[0]
61 ? self.seen.append(url)
62
63 ? links=r.parseAndGetLinks()
64 ? for eachLink in Links:
65 ? ?if eachLink[:4]!="http" and find(eachLink,"://")==-1
66 ? ? print "*",eachLink蚀乔,
以下鏈接將被忽略烁竭,不會被添加到待處理隊(duì)列里去的:屬于另外一個域的連接,已經(jīng)被下載過得鏈接吉挣,已經(jīng)放入待處理隊(duì)列里去的連接或者是"mailto:"連接派撕。
67 ? ?if find(lower(eachLink),"mailto:")!=-1:應(yīng)該是超連接
68 ? ? print "...discard,mailto link"
69 ? ? contine
70 ? ?if eachlink not in self.seen:
71 ? ? if find(eachLink,self.dom)==-1:
72 ? ? ?print "...discarded,not in domain"
73 ? ? else:
74 ? ? ?if eachLink not in self.q:
75 ? ? ? self.q.append(eachLink)
76 ? ? ? print "...new,aded to Q"
77 ? ? ?else:
78 ? ? ? print "...discarded,already in Q"
79 ? ?else:
80 ? ? print "...discarded,already processed"
81 ?def go(self):
82 ? while self.q:
83 ? ?url=self.q.pop()
84 ? ?self.getPage(url)
85 ?def main():
86 ? if len(argv)>1:
87 ? ?url=argv[1]
88 ? else:
89 ? ?try:
90 ? ? url=raw_input("Enter starting URL:")
91 ? ?except(KeyboardInterrupt,EOFError):
92 ? ? url=""
93 ? if not url: return
94 ? robot=Crawler(url)
95 ? robot.go()
96
97 if __name__=="__main__":
98 ?main()
99
main()只有在這個腳本程序在直接被調(diào)用時(shí)才會執(zhí)行,它是程序的出發(fā)點(diǎn)睬魂,其他導(dǎo)入了crawl.py的模塊需要明確的調(diào)用main()才能開始處理终吼。要讓main()開始執(zhí)行,需要給它一個URL氯哮,如果已經(jīng)在一個命令行給出URL(例如我們直接調(diào)用這個腳本程序的時(shí)候)际跪,它就會從給定的URL起開始運(yùn)行;否則喉钢,腳本程序?qū)⑦M(jìn)入交互模式姆打,提示用戶輸入一個URL。有了初始連接之后肠虽,程序?qū)rawler類進(jìn)行實(shí)例化并開始執(zhí)行幔戏。