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 Dormouses story</title></head>
- <body>
- <p class="title"><b>The Dormouses 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 van Rossum
数据格式:
标题格式:
<dd class="lemmaWgt-lemmaTitle-title"><h1>***</h1>***</dd>
简介格式:
<div class="lemma-summary" label-module="lemmaSummary">***</div>
页面编码: 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 van Rossum
- 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
来源: http://www.bubuko.com/infodetail-2504698.html