簡介:
網(wǎng)絡(luò)爬蟲(又被稱為網(wǎng)頁蜘蛛)撤蟆,網(wǎng)絡(luò)機器人,是一種按照一定的規(guī)則堂污,自動地抓信息的程序或者腳本家肯。假設(shè)互聯(lián)網(wǎng)是一張很大的蜘蛛網(wǎng),每個頁面之間都通過超鏈接這根線相互連接盟猖,那么我們的爬蟲小程序就能夠通過這些線不斷的搜尋到新的網(wǎng)頁讨衣。
Python作為一種代表簡單主義思想的解釋型、面向?qū)ο笫礁洹⒐δ軓姶蟮母呒壘幊陶Z言反镇。它語法簡潔并且具有動態(tài)數(shù)據(jù)類型和高層次的抽象數(shù)據(jù)結(jié)構(gòu),這使得它具有良好的跨平臺特性娘汞,特別適用于爬蟲等程序的實現(xiàn)歹茶,此外Python還提供了例如Spyder這樣的爬蟲框架,BeautifulSoup這樣的解析框架,能夠輕松的開發(fā)出各種復(fù)雜的爬蟲程序辆亏。
在這篇文章中风秤,使用Python自帶的urllib和BeautifulSoup庫實現(xiàn)了一個簡單的web爬蟲,用來爬取每個URL地址及其對應(yīng)的標題內(nèi)容扮叨。
流程:
- 爬蟲算法從輸入中讀取的一個URL作為初始地址缤弦,向該地址發(fā)出一個Request請求。
- 請求的地址返回一個包含所有內(nèi)容的彻磁,將其存入一個String變量碍沐,使用該變量實例化一個BeautifulSoup對象,該對象能夠?qū)?nèi)容并且將其解析為一個DOM樹衷蜓。
根據(jù)自己的需要建立正則表達式累提,最后借助HTML標簽從中解析出需要的內(nèi)容和新的URL,將新的放入隊列中磁浇。 - 對于目前所處的URL地址與爬去的內(nèi)容斋陪,在進行一定的過濾、整理后會建立索引置吓,這是一個單詞-頁面的存儲結(jié)構(gòu)无虚。當用戶輸入搜索語句后,相應(yīng)的分詞函數(shù)會對語句進行分解獲得關(guān)鍵詞衍锚,然后再根據(jù)每個關(guān)鍵詞查找到相應(yīng)的URL友题。通過這種結(jié)構(gòu),可以快速的獲取這個單詞所對應(yīng)的地址列表戴质。在這里使用樹形結(jié)構(gòu)的存儲方式度宦,Python的字典和列表類型能夠較好的構(gòu)建出單詞詞典樹。
- 從隊列中彈出目前的URL地址告匠,在爬取隊列不為空的條件下戈抄,算法不斷從隊列中獲取到新的網(wǎng)頁地址,并重復(fù)上述過程凫海。
實現(xiàn):
環(huán)境:
- Python 3.5 or Anaconda3
- BeautifulSoup 4
可以使用下面的指令安裝BeautifulSoup4呛凶,如果你是Ubuntu用戶,記得在命令前面加上sudo:
pip install beautifulsoup4
程序分別實現(xiàn)了幾個類行贪,分別用于URL地址管理,Html內(nèi)容請求模闲、Html內(nèi)容解析建瘫、索引建立以及爬蟲主進程。我將整個程序按照每個Class分開解釋尸折,最后只要將他們放在一起就可以執(zhí)行代碼了啰脚。
UrlManager類
這個類用來管理URL地址,new_urls用來保存還未爬取的URL地址, old_urls保存了已經(jīng)爬取過的地址橄浓,兩個變量都使用set類型保證其中內(nèi)容的唯一性粒梦。每次循環(huán)時,add_new_urls()向外提供了向new_urls變量中添加新urls的方法荸实;add_new_url()方法匀们,對每個url地址進行重復(fù)性檢查,符合條件的才進行添加操作准给;get_urls()向外提供了獲取新的url地址的方法泄朴;has_new_url()方法用來檢查爬取隊列是否為空。
import re
import urllib.request
import urllib.parse
from bs4 import BeautifulSoup
class UrlManager(object):
def __init__(self):
self.new_urls = set()
self.old_urls = set()
def add_new_url(self, url):
if url is None:
return
if url not in self.new_urls and url not in self.old_urls:
self.new_urls.add(url)
def add_new_urls(self, urls):
if urls is None or len(urls) == 0:
return
for url in urls:
self.add_new_url(url)
def has_new_url(self):
return len(self.new_urls) != 0
def get_new_url(self):
new_url = self.new_urls.pop()
self.old_urls.add(new_url)
return new_url
HtmlDownloader類
這個類實現(xiàn)了向url地址發(fā)送Request請求露氮,并獲取其回應(yīng)的方法祖灰,調(diào)用類內(nèi)的download()方法就可實現(xiàn)。這里要注意的是頁面的編碼問題畔规,這里我使用的是UTF-8來進行decode解碼局扶,有的網(wǎng)頁可能使用的是GBK編碼,要根據(jù)實際情況進行修改叁扫。
class HtmlDownloader(object):
def download(self, url):
if url is None:
return None
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8').encode('utf-8')
if content is None:
return None
if response.getcode() != 200:
return None
except urllib.request.URLError as e:
print(e)
return None
return content
HtmlParser類
這個類通過實例化一個BeautifulSoup對象來進行頁面的解析三妈。它是一個使用Python編寫的HTML/XML文檔解析器。它通過將文檔解析為DOM樹的方式為用戶提供需要抓取的數(shù)據(jù)陌兑,并且提供一些簡單的函數(shù)用來處理導航沈跨、搜索、修改分析樹等功能兔综。
該類的關(guān)鍵是_get_new_urls()饿凛、_get_new_content()、get_url_title()三個方法软驰。第一個方法用來解析出頁面包含的超鏈接涧窒,最為重要的選擇要解析的標簽并為其構(gòu)造合適的正則表達式。這里我為a標簽定義了一個匹配正則锭亏,用來獲取所有的站內(nèi)鏈接纠吴,如下:
links = soup.find_all('a', href=re.compile(r'^(%s).*(/|html)$' % self.domain))`
后面的兩個類都是通過解析Html標簽來獲取title的方法,最終在parse()中通過調(diào)取_get_new_content()來獲得title內(nèi)容慧瘤。具體的標簽訪問方法不細談了戴已,讀者可以自己翻閱BeautifulSoup的官方文檔。
class HtmlParser(object):
def __init__(self, domain_url):
self.domain = domain_url
self.res = HtmlDownloader()
def _get_new_urls(self, page_url, soup):
new_urls = set()
links = soup.find_all('a', href=re.compile(r'^(%s).*(/|html)$' % self.domain))
try:
for link in links:
new_url = link['href']
new_full_url = urllib.parse.urljoin(self.domain, new_url)
new_urls.add(new_full_url)
new_urls = list(new_urls)
return new_urls
except AttributeError as e:
print(e)
return None
def _get_new_content(self, page_url, soup):
try:
title_name = soup.title.string
return title_name
except AttributeError as e:
print(e)
return None
def get_url_title(self):
content = self.res.download(self.domain)
try:
soup = BeautifulSoup(content, 'html.parser', from_encoding='utf-8')
title_name = soup.title.string
return title_name
except:
title_name = 'None Title'
return title_name
def parse(self, page_url, html_cont):
if page_url is None or html_cont is None:
return None
soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
new_data = self._get_new_content(page_url, soup)
new_urls = self._get_new_urls(page_url, soup)
return new_urls, new_data
BuildIndex
該類為每個URL地址與他的標題包含的關(guān)鍵詞建立了一個索引關(guān)系并保存在一個Dict變量中锅减,每個標題對應(yīng)多個關(guān)鍵詞糖儡,每個標題也對應(yīng)多個url地址,因此每個關(guān)鍵詞也對應(yīng)了多個url地址怔匣,具體的形式如下:
index = {'keyword': [url1, url2, ... ,urln], ...}
其中握联,add_page_index()方法對每個標題進行了分詞處理,并且調(diào)用了 add_key_index()方法將 keyword-url的對應(yīng)關(guān)系存到索引中,這其中也進行了重復(fù)檢查金闽。主意纯露,這個分詞方法僅限于英文句子,中文的話需要用到特定的分詞工具代芜。
class BuildIndex(object):
def add_page_index(self, index, url, content):
words = content.split()
for word in words:
index = self.add_key_index(index, url, word)
return index
def add_key_index(self, index, url, keyword):
if keyword in index:
if url not in index[keyword]:
index[keyword].append(url)
else:
temp = []
index[keyword] = temp
index[keyword].append(url)
return index
SpiderMain
這是爬蟲的主題類埠褪,它通過調(diào)用其他幾個類生成的對象來實現(xiàn)爬蟲的運行。該類實例化的時候會永久生成上面幾個類的對象蜒犯,當通過craw()方法獲取到用戶提供的url地址時组橄,就會依次進行請求、下載罚随、解析玉工、建立索引的工作。最后該方法會返回index淘菩,graph兩個變量遵班,他們分別是:
每個關(guān)鍵詞集齊對應(yīng)的地址,keyword-urls索引潮改,如下
index = {'keyword': [url1, url2, ... ,urln], ...}
每個url及其頁面中包含的urls狭郑,url-suburls索引,如下
graph = {'url': [url1, url2, ... ,urln], ...}
class SpiderMain(object):
def __init__(self, root_url):
self.root_url = root_url
self.urls = UrlManager()
self.downloader = HtmlDownloader()
self.parser = HtmlParser(self.root_url)
self.build = BuildIndex()
def craw(self):
index = graph = {}
self.urls.add_new_url(self.root_url)
while self.urls.has_new_url():
try:
new_url = self.urls.get_new_url()
html_cont = self.downloader.download(new_url)
new_urls, new_title = self.parser.parse(new_url, html_cont)
index = self.build.add_page_index(index, new_url, new_title)
graph[new_url] = list(new_urls)
self.urls.add_new_urls(new_urls)
except Exception as e:
print(e)
return None
return index, graph
最后汇在,我們在程序中添加下面的代碼翰萨,就可以成功的執(zhí)行我們的爬蟲了
if __name__ == '__main__':
spider = SpiderMain('http://www.xael.org/')
index, graph = spider.craw()
print(index)
print(graph)