BeautifulSoup模块介绍和安装

BeautifulSoup安装很简单,我们可以直接使用pip来安装BeautifulSoup,安装命令如下:

pip install beautifulsoup4

如果使用的IDE是Pycharm的话,安装更简单,直接编写导入模块的语句:import bs4,然后会报错,提示模块不存在,接着按 alt + 回车,会出现错误修正提示,最后选择安装模块即可自动安装。

安装完成之后编写一段测试代码:

import bs4

print(bs4)

如果执行这段代码,并且正常输出没有报错则代表已经安装成功。

BeautifulSoup的语法:
使用Python的BeautifulSoup库实现一个可以爬取1000条百度百科数据的爬虫

访问节点信息:
使用Python的BeautifulSoup库实现一个可以爬取1000条百度百科数据的爬虫

语法格式:

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包,在该包下创建相应的模块文件,如下图:
使用Python的BeautifulSoup库实现一个可以爬取1000条百度百科数据的爬虫

  • 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()

运行效果:

控制台输出:
使用Python的BeautifulSoup库实现一个可以爬取1000条百度百科数据的爬虫

生成的html文件:
使用Python的BeautifulSoup库实现一个可以爬取1000条百度百科数据的爬虫

至此,我们一个简单的爬虫就完成了。

源码GitHub地址:

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