1. 程式人生 > >使用Python的BeautifulSoup庫實現一個可以爬取1000條百度百科數據的爬蟲

使用Python的BeautifulSoup庫實現一個可以爬取1000條百度百科數據的爬蟲

otto 提取數據 tps summary 簡介 標題格式 段落 字典 如果

BeautifulSoup模塊介紹和安裝
  • BeautifulSoup
    • BeautifulSoup是Python的第三方庫,用於從HTML或XML中提取數據,通常用作於網頁的解析器
    • BeautifulSoup官網: https://www.crummy.com/software/BeautifulSoup/
    • 官網文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/
    • 中文文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

BeautifulSoup安裝很簡單,我們可以直接使用pip來安裝BeautifulSoup,安裝命令如下:

pip install beautifulsoup4

如果使用的IDE是Pycharm的話,安裝更簡單,直接編寫導入模塊的語句:import bs4,然後會報錯,提示模塊不存在,接著按 alt + 回車,會出現錯誤修正提示,最後選擇安裝模塊即可自動安裝。

安裝完成之後編寫一段測試代碼:

import bs4

print(bs4)

如果執行這段代碼,並且正常輸出沒有報錯則代表已經安裝成功。

BeautifulSoup的語法:
技術分享圖片

訪問節點信息:
技術分享圖片

語法格式:

from bs4 import BeautifulSoup
import re

# 根據HTML網頁字符串內容創建BeautifulSoup對象
soup = BeautifulSoup(html_doc,              # HTML文檔字符串
                     ‘html.parser‘,         # HTML解析器
                     from_encoding=‘utf-8‘  # HTML文檔的編碼,在python3中不需要加上這個參數
                     )

# 方法:find_all(name, attrs, string)

# 查找所有標簽為 a 的節點
soup.find_all(‘a‘)

# 查找所有標簽為 a 的節點,並鏈接符合/view/123.html形式的節點
soup.find_all(‘a‘, href=‘/view/123.html‘)
soup.find_all(‘a‘, href=re.compile(‘/view/\d+\.html‘))

# 查找所有標簽為div,class為abc,標簽內容為Python的節點
soup.find_all(‘div‘, class_=‘abc‘, string=‘標簽內容為Python的節點‘)

# 得到節點:<a href=‘1.html‘>Python</a>

# 獲取查找到的節點的標簽名稱
node.name

# 獲取查找到的a節點的href屬性
node[‘href‘]

# 獲取查找到的a節點的鏈接文字
node.get_text()

實際的測試代碼:

from bs4 import BeautifulSoup
import re

html_doc = """
<html><head><title>The Dormouse‘s story</title></head>
<body>
<p class="title"><b>The Dormouse‘s story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

# 創建BeautifulSoup對象
soup = BeautifulSoup(html_doc, ‘html.parser‘)

print("獲取所有的連接")
links = soup.find_all(‘a‘)
for link in links:
    print(link.name, link[‘href‘], link.get_text())

print("\n獲取lacie的連接")
link_node = soup.find(‘a‘, href=‘http://example.com/lacie‘)
print(link_node.name, link_node[‘href‘], link_node.get_text())

print("\n使用正則表達式進行匹配")
link_node = soup.find(‘a‘, href=re.compile(r"ill"))
print(link_node.name, link_node[‘href‘], link_node.get_text())

print("\n獲取p段落文字")
p_node = soup.find(‘p‘, class_="title")
print(p_node.name, p_node.get_text())

實例爬蟲

簡單了解了BeautifulSoup並且完成了BeautifulSoup的安裝後,我們就可以開始編寫我們的爬蟲了。

我們編寫一個簡單的爬蟲一般需要完成以下幾個步驟:

  • 確定目標

    • 確定要爬取的網頁,例如本實例要爬取的是百度百科與Python相關的詞條網頁以及標題和簡介
  • 分析目標

    • 分析目標網頁的URL格式,避免抓取不相幹的URL
    • 分析要抓取的數據格式,例如本實例中要抓取的是標題和簡介等數據
    • 分析目標網頁的編碼,不然有可能在使用解析器解析網頁內容時會出現亂碼的情況
  • 編寫代碼

    • 分析完目標頁面後就是編寫代碼去進行數據的爬取
  • 執行爬蟲
    • 代碼編寫完成之後,自然是執行這個爬蟲,測試能否正常爬取數據

開始分析本實例需要爬取的目標網頁:

  • 目標:百度百科Python詞條相關詞條網頁-標題和簡介
  • 入口頁:https://baike.baidu.com/item/Python/407313
  • URL格式:
    • 詞條頁面URL:/item/name/id 或者 /item/name/,例:/item/C/7252092 或者 /item/Guido%20van%20Rossum
  • 數據格式:
    • 標題格式:
      • &lt;dd class="lemmaWgt-lemmaTitle-title"&gt;&lt;h1&gt;***&lt;/h1&gt;***&lt;/dd&gt;
    • 簡介格式:
      • &lt;div class="lemma-summary" label-module="lemmaSummary"&gt;***&lt;/div&gt;
  • 頁面編碼:UTF-8

分析完成之後開始編寫實例代碼

  • 該爬蟲需要完成的目標:爬取百度百科Python詞條相關1000個頁面數據

首先創建一個工程目錄,並在目錄下創建一個python包,在該包下創建相應的模塊文件,如下圖:
技術分享圖片

  • spider_main:爬蟲調度器程序,也是主入口文件
  • url_manager:url管理器,管理並存儲待爬取的url
  • html_downloader:下載器,用於下載目標網頁的內容
  • html_parser:解析器,解析下載好的網頁內容
  • html_outputer:輸出器,將解析後的數據輸出到網頁上或控制臺中

爬蟲調度器程序代碼:

‘‘‘
    爬蟲調度器程序,也是主入口文件
‘‘‘

import url_manager, html_downloader, html_parser, html_outputer

class SpiderMain(object):
    # 初始化各個對象
    def __init__(self):
        self.urls = url_manager.UrlManager()  # url管理器
        self.downloader = html_downloader.HtmlDownloader()  # 下載器
        self.parser = html_parser.HtmlParser()  # 解析器
        self.outputer = html_outputer.HtmlOutputer()  # 輸出器

    # 爬蟲調度方法
    def craw(self, root_url):
        # 記錄當前爬取的是第幾個URL
        count = 1
        # 將入口頁面的url添加到url管理器裏
        self.urls.add_new_url(root_url)

        # 啟動爬蟲的循環
        while self.urls.has_new_url():
            try:
                # 獲取待爬取的url
                new_url = self.urls.get_new_url()

                # 每爬取一個頁面就在控制臺打印一下
                print("craw", count, new_url)

                # 啟動下載器來下載該url的頁面內容
                html_cont = self.downloader.download(new_url)

                # 調用解析器解析下載下來的頁面內容,會得到新的url列表及新的數據
                new_urls, new_data = self.parser.parse(new_url, html_cont)

                # 將新的url列表添加到url管理器裏
                self.urls.add_new_urls(new_urls)

                # 收集解析出來的數據
                self.outputer.collect_data(new_data)

                # 當爬取到1000個頁面時則停止爬取
                if count == 1000:
                    break

                count += 1

            except:
                # 爬取時出現異常則在控制臺中輸出一段文字
                print("craw failed")

        # 輸出處理好的數據
        self.outputer.output_html()

# 判斷本模塊是否作為入口文件被執行
if __name__ == "main":
    # 目標入口頁面的URL
    root_url = "https://baike.baidu.com/item/Python/407313"
    obj_spider = SpiderMain()
    # 啟動爬蟲
    obj_spider.craw(root_url)

url管理器代碼:

‘‘‘
    url管理器,管理並存儲待爬取的url。

    url管理器需要維護兩個列表,一個是
    待爬取的url列表,另一個是已爬取的
    url列表。
‘‘‘

class UrlManager(object):
    def __init__(self):
        self.new_urls = set()  # 待爬取的url列表
        self.old_urls = set()  # 已爬取的url列表

    def add_new_url(self, url):
        ‘‘‘
        向管理器中添加新的url,也就是待爬取的url
        :param url: 新的url
        :return:
        ‘‘‘
        # url為空則結束
        if url is None:
            return

        # 該url不在兩個列表中才是新的url
        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):
        ‘‘‘
        向管理器中批量添加新的url
        :param urls: 新的url列表
        :return:
        ‘‘‘
        if urls is None or len(urls) == 0:
            return

        for url in urls:
            self.add_new_url(url)

    def has_new_url(self):
        ‘‘‘
        判斷管理器中是否有待爬取的url
        :return: True 或 False
        ‘‘‘
        return len(self.new_urls) != 0

    def get_new_url(self):
        ‘‘‘
        從url管理器中獲取一個待爬取的url
        :return: 返回一個待爬取的url
        ‘‘‘
        # 出棧一個url,並將該url添加在已爬取的列表中
        new_url = self.new_urls.pop()
        self.old_urls.add(new_url)

        return new_url

下載器代碼:

‘‘‘
    下載器,用於下載目標網頁的內容
‘‘‘

from urllib import request

class HtmlDownloader(object):
    def download(self, url):
        ‘‘‘
        下載url地址的頁面內容
        :param url: 需要下載的url
        :return: 返回None或者頁面內容
        ‘‘‘
        if url is None:
            return None

        response = request.urlopen(url)
        if response.getcode() != 200:
            return None

        return response.read()

解析器代碼:

‘‘‘
    解析器,解析下載好的網頁內容
‘‘‘
import re
import urllib.parse

from bs4 import BeautifulSoup

class HtmlParser(object):
    def parse(self, page_url, html_cont):
        ‘‘‘
        解析下載好的網頁內容
        :param page_url: 頁面url
        :param html_cont: 網頁內容
        :return: 返回新的url列表及解析後的數據
        ‘‘‘
        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

    def _get_new_urls(self, page_url, soup):
        ‘‘‘
        得到新的url列表
        :param page_url:
        :param soup:
        :return:
        ‘‘‘
        new_urls = set()

        # 詞條頁面URL:/item/name/id 或者 /item/name/,例:/item/C/7252092 或者 /item/Guido%20van%20Rossum
        links = soup.find_all(‘a‘, href=re.compile(r"/item/(.*)"))
        for link in links:
            new_url = link[‘href‘]
            # 拼接成完整的url
            new_full_url = urllib.parse.urljoin(page_url, new_url)
            new_urls.add(new_full_url)

        return new_urls

    def _get_new_data(self, page_url, soup):
        ‘‘‘
        解析數據,並返回解析後的數據
        :param page_url:
        :param soup:
        :return:
        ‘‘‘
        # 使用字典來存放解析後的數據
        res_data = {}

        # url
        res_data[‘url‘] = page_url

        # 標題標簽格式:<dd class="lemmaWgt-lemmaTitle-title"><h1>***</h1>***</dd>
        title_node = soup.find(‘dd‘, class_=‘lemmaWgt-lemmaTitle-title‘).find(‘h1‘)
        res_data[‘title‘] = title_node.get_text()

        # 簡介標簽格式:<div class="lemma-summary" label-module="lemmaSummary">***</div>
        summary_node = soup.find(‘div‘, class_=‘lemma-summary‘)
        res_data[‘summary‘] = summary_node.get_text()

        return res_data

輸出器代碼:

‘‘‘
    輸出器,將解析後的數據輸出到網頁上
‘‘‘

class HtmlOutputer(object):
    def __init__(self):
        # 存儲解析後的數據
        self.datas = []

    def collect_data(self, data):
        ‘‘‘
        收集數據
        :param data:
        :return:
        ‘‘‘
        if data is None:
            return

        self.datas.append(data)

    def output_html(self):
        ‘‘‘
        將收集的數據以html的格式輸出到html文件中,我這裏使用了Bootstrap
        :return:
        ‘‘‘
        fout = open(‘output.html‘, ‘w‘, encoding=‘utf-8‘)

        fout.write("<!DOCTYPE html>")
        fout.write("<html>")
        fout.write(‘<head>‘)
        fout.write(‘<meta charset="UTF-8" />‘)
        fout.write(
            ‘<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"/>‘)
        fout.write(‘</head>‘)
        fout.write("<body>")
        fout.write(
            ‘<div style="width: 1000px;margin: auto" class="bs-example" data-example-id="bordered-table" ><table class="table table-bordered table-striped" >‘)
        fout.write(
            ‘<thead><tr style="height: 70px;font-size: 20px"><th style="text-align: center;vertical-align: middle;width: 60px">#</th><th style="text-align: center;vertical-align: middle;width: 150px">URL & 標題</th><th style="text-align: center;vertical-align: middle;">簡介</th></tr></thead><tbody>‘)

        num = 0
        for data in self.datas:
            fout.write("<tr>")
            fout.write("<th style=‘text-align: center;vertical-align: middle;‘ scope=‘row‘>%d</th>" % num)
            fout.write("<td style=‘text-align: center;vertical-align: middle;‘><a href=%s>%s</a></td>" % (
                data[‘url‘], data[‘title‘]))
            fout.write("<td>%s</td>" % data[‘summary‘])
            fout.write("</tr>")
            num += 1

        fout.write("</tbody></table></div>")
        fout.write("</body>")
        fout.write("</html>")

        fout.close()

運行效果:

控制臺輸出:
技術分享圖片

生成的html文件:
技術分享圖片

至此,我們一個簡單的爬蟲就完成了。

源碼GitHub地址:

https://github.com/Binary-ZeroOne/easy-spider

使用Python的BeautifulSoup庫實現一個可以爬取1000條百度百科數據的爬蟲