Python實現(xiàn)簡易Web爬蟲

簡介:

網(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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市糕殉,隨后出現(xiàn)的幾起案子亩鬼,更是在濱河造成了極大的恐慌,老刑警劉巖阿蝶,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雳锋,死亡現(xiàn)場離奇詭異垃喊,居然都是意外死亡靴寂,警方通過查閱死者的電腦和手機市框,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門缘挽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人油啤,你說我怎么就攤上這事色罚】咂耍” “怎么了真仲?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵嚼隘,是天一觀的道長。 經(jīng)常有香客問我袒餐,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任灸眼,我火速辦了婚禮卧檐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焰宣。我一直安慰自己霉囚,他們只是感情好,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布匕积。 她就那樣靜靜地躺著盈罐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闪唆。 梳的紋絲不亂的頭發(fā)上盅粪,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音悄蕾,去河邊找鬼票顾。 笑死,一個胖子當著我的面吹牛帆调,可吹牛的內(nèi)容都是我干的奠骄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼番刊,長吁一口氣:“原來是場噩夢啊……” “哼含鳞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起芹务,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蝉绷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锄禽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潜必,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年沃但,在試婚紗的時候發(fā)現(xiàn)自己被綠了磁滚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡宵晚,死狀恐怖垂攘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淤刃,我是刑警寧澤晒他,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站逸贾,受9級特大地震影響陨仅,放射性物質(zhì)發(fā)生泄漏津滞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一灼伤、第九天 我趴在偏房一處隱蔽的房頂上張望触徐。 院中可真熱鬧,春花似錦狐赡、人聲如沸撞鹉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸟雏。三九已至,卻和暖如春览祖,著一層夾襖步出監(jiān)牢的瞬間孝鹊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工穴墅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惶室,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓玄货,卻偏偏與公主長得像皇钞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子松捉,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容