python爬蟲實戰:基礎爬蟲(使用BeautifulSoup4等)

  之前學習寫爬蟲程序時候,我沒有系統地學習爬蟲最基本的模塊框架,只是實現本身的目標而寫出來的,最近學習基礎的爬蟲,但含有完整的結構,大型爬蟲含有的基礎模塊,此項目也有,「麻雀雖小,五臟俱全」,只是沒有考慮優化和穩健性問題。html

              爬蟲框架node

爬蟲框架包括這五大模塊,簡單介紹做用:1.爬蟲調度器:協調其餘四大模塊工做;2.URL管理器:就是管理提供爬取的連接,分爲已爬取URL集合和未爬取URL集合;3.html下載器:下載URL的整個html網頁;4.html解析器:將下載的網頁進行解析,得到有效數據;5.數據存儲器:存儲解析後的數據,以文件或數據庫形式存儲。python

項目準備爬取百度百科的一些名詞解釋和連接,這是小的項目,許多方法簡化處理,作起來簡潔有效。下面是具體步驟:正則表達式

一、URL管理器數據庫

  根據做用可知,它包括2個集合,已爬取和未爬取URL連接,因此使用python的set()類型進行去重,防止重複死循環。這裏在實踐時候我存在疑問,後面再討論。去重方案主要有3種:1)內存去重,2)關係數據庫去重,3)緩存數據庫去重;明顯地,大型成熟爬蟲會選擇後兩種,避免內存大小限制,而如今還沒有成熟的小項目,就使用第1種。緩存

class UrlManager():  # URL管理器
    def __init__(self):
        self.new_urls = set()  # 未爬取集合(去重)
        self.old_urls = set()

    def has_new_url(self):
        return self.new_url_size() != 0  # 判斷是否有未爬取

    def get_new_url(self):
        new_url = self.new_urls.pop()
        self.old_urls.add(new_url)
        return new_url

    def add_new_url(self, url):  # 將新的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 new_url_size(self):
        # print(len(self.new_urls))
        return len(self.new_urls)


    def old_url_size(self):
        return len(self.old_urls)

幾個函數做用很清晰,它會將新的連接加入未爬取集合,爬取過的URL就存入old_urls集合中。app

2、HTML下載器框架

這裏原本用requests包操做,可是我在後面運行程序時出現錯誤,因此後來我用了urllib包代替,而requests是它的高級封裝,按往常也是用這個,具體是函數返回值出問題仍是其餘緣由還沒找出來,給出2個方法,須要你們指正。ide

import urllib.request

class HtmlDownloader(object):
    def download(self, url):
        if url is None:
            return None
        user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
        headers = {'User-Agent': user_agent}
        html_content = urllib.request.Request(url,headers=headers)#urlopen(url)
        response=urllib.request.urlopen(html_content)
        if response.getcode() == 200:
            # print(response.read())
            return response.read()#.decode('utf-8')
        return None

這個是成功的方法,使用urllib打開URL連接,添加請求頭能夠更好的模擬正常訪問,根據狀態碼返回內容。而存在問題的requests以下:模塊化

import requests#使用requests包爬取,結果提示頁面不存在

class HtmlDownloader(object):  # HTML下載器
    def download(self, url):
        if url is None:
            return None
        user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
        headers = {'User-Agent': user_agent}
        respondse = requests.get(url, headers=headers)
        # print(url)
        if respondse.status_code != 200:
            return None
        # respondse.encoding = 'utf-8'
        # print(respondse.content)
        return respondse.content

測試時候可看出urllib的respondse.read()與requests的content都是bytes類型,從代碼閱讀上大致同樣,通過幾回嘗試,覺得是編解碼問題,最後仍是沒能找出問題,因此用了urllib進行。

三、HTML下載器

   這裏使用BeautifulSoup4進行解析,bs4進行解析能夠很簡潔的幾行代碼完成,功能強大常使用。先分析網頁結構格式,寫出匹配表達式獲取數據。

分析以後標題title = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')。獲取tag中包含的全部文本內容,包括子孫tag中的內容,並將結果做爲Unicode字符串返回,詞條解釋可寫出式子匹配summary = soup.find('div', class_='lemma-summary')。

import re
from urllib.parse import urljoin
from bs4 import BeautifulSoup


class HtmlParser(object):
    def _get_new_data(self, page_url, soup):
        data = {}
        data['url'] = page_url
        title = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')
        data['title'] = title.get_text()
        summary = soup.find('div', class_='lemma-summary')
        data['summary'] = summary.get_text()
        # print(summary_node.get_text())
        return data

    def _get_new_urls(self, page_url, soup):
        new_urls = set()
        # print(new_urls)
        links = soup.find_all('a', href=re.compile('/item/\w+'))

        for link in links:
            new_url = link['href']
            new_full_url = urljoin(page_url, new_url)
            new_urls.add(new_full_url)
            # print(new_full_url)
        return new_urls

    def parser(self, page_url, html_cont):
        if page_url is None or html_cont is None:
            return
        soup = BeautifulSoup(html_cont, 'html.parser') 
        new_urls = self._get_new_urls(page_url, soup)
        new_data = self._get_new_data(page_url, soup)
        return new_urls, new_data

 這裏使用了正則表達式來匹配網頁中的詞條連接:links = soup.find_all('a', href=re.compile('/item/\w+'))

四、數據存儲器

  包括兩個方法,store_data:將目標數據存到列表中;out_html:數據輸出外存,這裏存爲html格式,也能夠根據須要存爲csv、txt形式。

import codecs


class DataOutput(object):#數據存儲器
    def __init__(self):
        self.datas=[]

    def store_data(self,data):
        if data is None:
            return
        self.datas.append(data)

    def output_html(self):
        fout=codecs.open('baike.html','w',encoding='utf-8')
        fout.write("<html>")
        fout.write("<head><meta charset='utf-8'/></head>")
        fout.write("<body>")
        fout.write("<table>")
        for data in self.datas:
            print(data["title"])
            fout.write("<tr>")
            fout.write("<td>%s</td>"%data['url'])
            fout.write("<td>%s</td>"%data['title'])
            fout.write("<td>%s</td>" % data['summary'])
            fout.write("</tr>")
            # self.datas.remove(data)
        fout.write("</table>")
        fout.write("</body>")
        fout.write("</html>")
        fout.close()

爬取數據量少的狀況下可用以上方法,不然要使用分批存儲,避免發生異常,數據丟失。

五、爬蟲調度器

  終於到最後步驟了,也是關鍵的一步,協調上面全部「器」,爬蟲開始!

from craw_pratice.adataOutput import DataOutput
from craw_pratice.ahtmlDownloader import HtmlDownloader
from craw_pratice.ahtmlParser import HtmlParser
from craw_pratice.aurlManeger import UrlManager


class spiderman(object):
    def __init__(self):
        self.manage = UrlManager()
        self.downloader = HtmlDownloader()
        self.parser = HtmlParser()
        self.output = DataOutput()

    def crawl(self, root_url):
        self.manage.add_new_url(root_url)
        # 判斷URL管理器中是否有新的URL,同時判斷抓去了多少個URL
        while (self.manage.has_new_url() and self.manage.old_url_size() < 100):
            try:
                new_url = self.manage.get_new_url()  # 從管理器獲取新的URL
                print("已經抓取%s個連接: %s" % (self.manage.old_url_size(), new_url))
                html = self.downloader.download(new_url)  # 下載網頁
                # print(html)
                new_urls, data = self.parser.parser(new_url, html)  # 解析器抽取網頁數據
                print(data)
                self.manage.add_new_urls(new_urls)
                self.output.store_data(data)  # 存儲
            except Exception:

                print("crawl failed")
        self.output.output_html()


if __name__ == "__main__":
    root_url = 'https://baike.baidu.com/item/Python/407313'
    spider_man = spiderman()
    spider_man.crawl(root_url) 

至此,整個爬蟲項目完成了,效果如圖:

 這是我成功後的小總結,而過程並非如此順利,而是遇到小問題,對程序代碼不斷debug,好比:

上面說到的requests問題,致使爬取的連接不存在,一直提示頁面不存在。後來採起urllib解決。還有第3中urljoin的調用,整個小爬蟲項目我用到的是python3.6,已經把urlparse模塊封裝到urllib裏面,因此不採用import parser。

這個項目實踐讓我學習到爬蟲最基本的框架,各個功能都實現模塊化,清晰簡潔,爲以後實現大型成熟的爬蟲項目作了鋪墊,分享學習心得,但願能學得更好,要繼續努力!

相關文章
相關標籤/搜索