初始bs4
官网教程地址:https://beautifulsoup.readthedocs.io/zh-cn/v4.4.0/
lxml、pyquery、bs4、re执行效率对比执行速度对比:https://www.jianshu.com/p/d9812bbce6b6
安装
# 二选一即可
pip install bs4
pip install beautifulsoup4
# 建议安装lxml并设置为解析库,速度比html.parser快很多,容错能力也更强
pip install lxml
读取本地文件
from bs4 import BeautifulSoup
# 这里的第一个参数如果是本地文件,可以直接通过这种方法打开
# 这里的第二个参数就是解析库
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
print(type(soup)) <class 'bs4.BeautifulSoup'>
通过 name 获取标签名
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
print(soup.a.name) # a
print(soup.div.name) # div
通过 .标签名 获取数据
语法:bs4.BeautifulSoup.tagName
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 等价
print(soup.head.title) # <title>xx读书 Top 250</title>
print(soup.find('head').find('title')) # <title>xx读书 Top 250</title>
print(soup.img) # <img src="./xx读书 Top 250_files/s1070959.jpg" width="90"/>
print(soup.p) # <p class="appintro-title">xx</p>
print(soup.div)
print(soup.a) # <a class="nav-login" href="https://accounts.xx.com/passport/login?source=book" rel="nofollow">登录/注册</a>
获取属性
- 通过attrs
- 通过[属性名]
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
print(soup.a.attrs) # {'href': 'https://accounts.xx.com/passport/login?source=book', 'class': ['nav-login'], 'rel': ['nofollow']}
print(soup.a['href']) # https://accounts.xx.com/passport/login?source=book
print(soup.img.attrs) # {'src': './xx读书 Top 250_files/s1070959.jpg', 'width': '90'}
print(soup.img['src']) # ./xx读书 Top 250_files/s1070959.jpg
获取子节点 children 和子孙节点 descendants
children 方法只遍历当前节点的直接子节点,即只遍历当前节点的下一级子节点。
descendants 方法会递归遍历当前节点的所有子孙节点,包括子节点、孙子节点,以及更深层次的后代节点。
# 懒得敲 GPT写的 OVO
from bs4 import BeautifulSoup
# 示例 HTML 代码
html_doc = """
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<div id="content">
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<div>
<span>Span 1</span>
<span>Span 2</span>
</div>
</div>
</body>
</html>
"""
# 创建 BeautifulSoup 对象
soup = BeautifulSoup(html_doc, 'html.parser')
# 使用 children 遍历当前节点的直接子节点
print("Using children:")
for child in soup.body.children:
if child.name:
print(child.name)
# 使用 descendants 遍历当前节点的所有子孙节点
print("\nUsing descendants:")
for descendant in soup.body.descendants:
if descendant.name:
print(descendant.name)
获取父节点 parent,所有父节点parents
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 打印示例节点
print(soup.select(".item td")[1].prettify())
"""
<td valign="top">
这里省略了部分代码
<p class="quote" style="margin: 10px 0; color: #666">
<span class="inq">
都云作者痴,谁解其中味?
</span>
</p>
</td>
"""
# 打印子节点
print(soup.find('span', 'inq')) # <span class="inq">都云作者痴,谁解其中味?</span>
# 打印父节点
print(soup.find('span', 'inq').parent)
"""
<p class="quote" style="margin: 10px 0; color: #666">
<span class="inq">都云作者痴,谁解其中味?</span>
</p>
"""
只拿1条数据 find
- 如果只是一个标签,默认拿到符合标签的第一个结果。
soup.find(tagName)
- 也可以通过前面标签名,后面条件的方式更灵活的去筛选。
soup.find(tagName, 条件)
- 如果查找的条件是一个的属性是类
class
,不能直接写class
,会报错,可以参考下面两种写法:
soup.find('div', 'pl2')
soup.find('div', class_='pl2')
- 我们前面学到了,attrs可以拿到属性,那么条件也可以通过指定attrs字典更灵活的去筛选
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 相当于 soup.p
print(soup.find('p')) # <p class="appintro-title">xx</p>
print(soup.p) # <p class="appintro-title">xx</p>
# 下面两种写法是一样的
r1 = soup.find('div', 'pl2')
r2 = soup.find('div', class_='pl2')
print(r1 == r2) # True
# 通过attrs实现更灵活的查找
# 如果标签包含多个属性,这里的attrs也可以添加多个
# print(soup.find('div', attrs={'class': 'pl2'})) # 查找class属性为pl2的div标签
# 这个案例不太合适 一般是通过id或者class去定位的 不过确实能这样操作
print(soup.find('td', attrs={'width': '100', 'valign': 'top'}))
# 小练习 获取图片的src
print(soup.find('a', 'nbg').img.attrs)
print(soup.find('a', 'nbg').img['src']) # ./xx读书 Top 250_files/s1070959.jpg
print(soup.find('a', 'nbg').img.attrs.get('src')) # ./xx读书 Top 250_files/s1070959.jpg 这种写法会更麻烦一点
获取文本内容
- string 如果结果不包含额外的子标签,直接拿到内容,否则得到一个None
- text 拿到筛选条件的全部文本,如果内部还有子标签也包含直接文本,也能获取到。
- get_text() 同text 只是一这个是一个方法
- contents 获取文本内容,结果是列表显示
- 注意:字符串没有.contents 属性,因为字符串没有子节点。
- strings 拿到的是一个迭代器,可以通过
next
或者变量去取值- stripped_strings是一个生成器,用于获取标签内所有文本内容,并去除每个文本的前后空白字符(包括空格、换行符等)。但是,并不会去除文本中间的空格字符,可以获取到结果后通过正则或者字符串的其他方法进一步处理。
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
print(soup.find('div', 'pl2').string) # None 因为包含其他的子标签
print(soup.find('p', 'pl').string) # [清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元 这里能拿到结果因为换了一个标签
print(soup.find('div', class_='pl2').text)
"""
[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元
红楼梦
"""
# soup.text 同 soup.get_text()
print(soup.find('p', 'pl').text) # [清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元
print(soup.find('p', 'pl').get_text()) # [清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元
# 使用contents拿到文本结果,结果放列表种
print(soup.find('p', 'pl').contents) # ['[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元']
# 拿到可迭代对象
g = soup.find('p', 'pl').strings
print(next(g)) # [清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元
# 去掉换行和空格 很常用 推荐掌握
# 有的时候并
g = soup.find('tr', 'item').stripped_strings
print(list(g))
# ['红楼梦', '[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元', '9.6', '(\n 421411人评价\n )', '都云作者痴,谁解其中味?']
美化匹配结果 prettify
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 未美化
print(soup.find('div', attrs={"class", 'pl2'}))
"""
<div class="pl2">
<a href="https://book.xx.com/subject/1007305/" onclick=""moreurl(this,{i:'0'})"" title="红楼梦">
红楼梦
</a>
<img alt="可试读" src="./xx读书 Top 250_files/read.gif" title="可试读"/>
</div>
"""
# 美化后
print(soup.find('div', 'pl2').prettify())
"""
<div class="pl2">
<a href="https://book.xx.com/subject/1007305/" onclick=""moreurl(this,{i:'0'})"" title="红楼梦">
红楼梦
</a>
<img alt="可试读" src="./xx读书 Top 250_files/read.gif" title="可试读"/>
</div>
"""
获取所有结果 find_all
- find_all 返回的结果是一个列表,如果拿不到不会报错,而是得到一个空列表
[ ]
- 可以通过
limit
参数,控制获取结果的条数
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 获取class属性为 pl2 的div标签的全部结果
print(soup.find_all('div', 'pl2'))
# 通过切片获取前2条结果
print(soup.find_all('div', class_='pl2')[:2])
# 通过limit去获取结果 学过数据库的应该对这个不陌生 这个参数需要写在括号里面
print(soup.find_all('div', 'pl2', limit=2))
# 获取多个标签
# 注意 这里面只能放标签名称
print(soup.find_all(['p', 'img']))
css选择器 select
css选择器能使用的这里都能使用,得到的结果是一个列表,如果拿不到结果返回空列表,不会报错。
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 拿到全部a标签
print(soup.select('a'))
# 类选择器 选择class为pl2的全部标签
print(soup.select('.pl2'))
# id选择器 获取 id为db-global-nav 的全部结果
print(soup.select('#db-global-nav'))
# 如果要拿到结果可以通过索引取值
print(soup.select('#db-global-nav')[0])
# 只拿一个结果,这样就不用在索引取值了
print(soup.select_one('title')) # <title>xx读书 Top 250</title>
# 儿子选择器 > 拿到class属性为pl2的下面的第一个a
print(soup.select('.pl2>a'))
# 可以一直选 拿到class属性为pl2标签下面的第一个a标签,下面的第一个img标签
print(soup.select('.pl2>a>img'))
# 后代选择器 全部子孙
print(soup.select('.pl2 span'))
# 属性选择器
# 属性选择器就是 标签名[属性名=属性值]
print(soup.select('div[class="pl2"]'))
# 使用select()方法查询soup中所有带有class属性为"item"的tr标签下的p标签中的span标签,且这些span标签带有class属性为"inq"
print(soup.select('tr[class="item"] p.quote>span[class="inq"]'))
更多选择可以参考我的博客:点我跳转
通过正则去匹配
import re
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('xx读书 Top 250.html', encoding='utf-8'), 'lxml')
# 查询所有网址种包含 subject 字段的链接
print(soup.find_all(href=re.compile('subject')))
# 定义正则匹配规则
pattern = re.compile(r'.*?(\d+人评价).*?', re.S)
# 案例1
print(soup.find('span', 'pl').string)
"""
(
421411人评价
"""
# 案例2
print(soup.find('span', string=pattern).string)
"""
(
421411人评价
"""
获取注释 comments 了解即可
from bs4 import BeautifulSoup
# 示例 HTML 代码,包含注释内容
html_doc = """
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<!-- This is a comment -->
<div id="content">
<p>Paragraph 1</p>
<!-- Another comment -->
<p>Paragraph 2</p>
</div>
</body>
</html>
"""
# 创建 BeautifulSoup 对象
soup = BeautifulSoup(html_doc, 'html.parser')
# 使用 comments 方法获取注释内容
comments = soup.find_all(string=lambda text: isinstance(text, Comment))
# 输出注释内容
for comment in comments:
print(comment)
bs4练习
import threading
from pathlib import Path
from itertools import zip_longest
from concurrent.futures import ThreadPoolExecutor
import requests
from openpyxl import Workbook
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
# 获取当前文件所在路径
file_path = Path(__file__).parent
# 构建xx图书Top250各页的URL列表
url_list = [
f'https://book.xx.com/top250?start={page * 25}'
for page in range(10)
]
# 创建互斥锁,用于保护共享资源
lock = threading.Lock()
# 共享资源,用于计数已经处理的页面数量
shared_resource = 0
# 创建一个工作簿和工作表
wb = Workbook()
ws = wb.active
# 添加表头
ws.append(['书名', '书籍链接', '评分', '参与评分人数', '作者和价格', '一句话简介'])
def get_request(url):
"""
发送HTTP请求获取页面内容
Args:
url (str): 要请求的URL
Returns:
bytes: 页面内容的字节串,或者None(如果请求失败)
"""
try:
# 发送HTTP GET请求
response = requests.get(url=url, headers={'User-Agent': UserAgent().random})
response.raise_for_status() # 如果响应状态码不是200,则抛出异常
except requests.exceptions.RequestException:
pass # 如果请求出现异常,不做任何处理
else:
return response.content # 返回页面内容的字节串
def fetch_content(s):
"""
解析页面内容,并将解析结果写入Excel表格
Args:
s (concurrent.futures.Future): 表示一个异步操作的Future对象
"""
global shared_resource
data = s.result() # 获取异步操作的结果(即页面内容)
with lock:
shared_resource += 1 # 增加已处理页面的数量
# 使用BeautifulSoup解析页面内容
soup = BeautifulSoup(data, 'lxml')
# 获取页面中各项信息
title_name = soup.select(".item .pl2 a") # 书名
link_name = soup.select(".item .pl2 a") # 书籍链接
star_name = soup.select('.rating_nums') # 评分
evaluate_name = soup.select('.star>span.pl') # 参与评分人数
detail_name = soup.find_all('p', 'pl') # 作者和价格
inq_name = soup.find_all('span', attrs={'class': 'inq'}) # 一句话简介
# 将信息写入Excel表格
for title, link, star, evaluate, detail, inq in zip_longest(title_name, link_name, star_name, evaluate_name,
detail_name, inq_name):
# 处理空数据
if title is None:
title = '/'
else:
title = ''.join(title.text.split())
link = link['href']
star = star.string
evaluate = evaluate.string.split()[1].replace('人评价', '')
detail = detail.string
# 处理空数据
if inq is None:
inq = '/'
else:
inq = inq.string
# 写入Excel表格
ws.append([title, link, star, evaluate, detail, inq])
if __name__ == '__main__':
# 使用线程池执行HTTP请求
with ThreadPoolExecutor(max_workers=10) as t:
futures = [t.submit(get_request, url) for url in url_list]
# 处理每个页面的响应内容
for future in futures:
future.add_done_callback(fetch_content)
# 保存Excel文件
wb.save(rf"{file_path}/xx读书top250结果.xlsx")
import base64
from threading import Thread
import requests
from bs4 import BeautifulSoup
from pathlib import Path
from fake_useragent import UserAgent
class SpiderMixin(Thread):
BASE_DIR = Path(__file__).parent
base_url = 'https://www.xx.com/'
def __init__(self, url):
super().__init__()
self.url = url
@staticmethod
def random_header():
headers = {
'User-Agent': UserAgent().random
}
return headers
def get_request(self):
try:
response = requests.get(url=self.url, headers=SpiderMixin.random_header())
response.encoding = 'utf-8' if response.encoding == 'utf-8' else response.apparent_encoding
response.raise_for_status()
except requests.exceptions.RequestException:
pass
else:
return response.text
def encryption(self):
url = ''
return base64.b64decode(url).decode()
class FetchPage(SpiderMixin):
title = ''
url_list = []
def __init__(self, url):
super().__init__(url)
self.url = url
self.get_url()
def get_url(self):
html_doc = self.get_request()
soup = BeautifulSoup(html_doc, 'lxml')
title = soup.h1.string
self.title = title
link_list = soup.select(".book-mulu a")
for link in link_list:
novel_link = self.base_url + link['href']
self.url_list.append(novel_link)
def create_dir(self):
file_name = self.BASE_DIR / self.title
file_name.mkdir(parents=True, exist_ok=True)
class BookSpider(SpiderMixin):
def __init__(self, url, file_name):
super().__init__(url)
self.url = url
self.file_name = file_name
def run(self):
self.fetch_content()
def fetch_content(self):
content = self.get_request()
soup = BeautifulSoup(content, 'lxml')
title = soup.h1.string
article_content = soup.select(".chapter_content p")
with open(rf'{self.BASE_DIR}/{self.file_name}/{title}.txt', 'wt', encoding='utf-8') as file:
for item in article_content:
content = ''.join(list(item.stripped_strings))
file.write(f"{content}\n")
if __name__ == '__main__':
url = 'https://www.xx.com/book/suitangyanyi.html'
page = FetchPage(url=url)
title = page.title
page.create_dir()
for link in page.url_list:
t = BookSpider(url=link, file_name=title)
t.start()
本文作者:小满三岁啦
本文链接:https://www.cnblogs.com/ccsvip/p/18101795
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。