【Python爬虫】Python爬虫三大基础模块(urllib & BS4 & Selenium)
参考资料
-
Python爬虫教程(从入门到精通)
-
Python urllib | 菜鸟教程
-
Beautiful Soup 4 入门手册_w3cschool
-
Selenium入门指南
-
Selenium教程
-
什么是 Scrapy|极客教程
-
Scrapy入门教程
一、Python爬虫的基本知识
1、网络爬虫是什么?
我们所熟悉的一系列搜索引擎都是大型的网络爬虫,比如百度、搜狗、360浏览器、谷歌搜索等等。每个搜索引擎都拥有自己的爬虫程序,比如 360 浏览器的爬虫称作 360Spider,搜狗的爬虫叫做 Sogouspider。
1)爬虫分类
爬虫可分为三大类:通用网络爬虫、聚焦网络爬虫、增量式网络爬虫。
-
通用网络爬虫:
是搜索引擎的重要组成部分,上面已经进行了介绍,这里就不再赘述。通用网络爬虫()需要遵守
robots
协议,网站通过此协议告诉搜索引擎哪些页面可以抓取,哪些页面不允许抓取。robots 协议:是一种“约定俗称”的协议,并不具备法律效力,它体现了互联网人的“契约精神”。行业从业者会自觉遵守该协议,因此它又被称为“君子协议”。
为了限制爬虫带来的危险,大多数网站都有良好的反爬措施,并通过
robots.txt
协议做了进一步说明,下面是淘宝网robots.txt
的内容:User-agent: Baiduspider Disallow: /baidu Disallow: /s? Disallow: /ulink? Disallow: /link? Disallow: /home/news/data/ Disallow: /bh ..... User-agent: * Disallow: /
从协议内容可以看出,淘宝网对不能被抓取的页面做了规定。因此大家在使用爬虫的时候,要自觉遵守 robots 协议
-
聚焦网络爬虫:
是面向特定需求的一种网络爬虫程序。它与通用爬虫的区别在于,聚焦爬虫在实施网页抓取的时候会对网页内容进行筛选和处理,尽量保证只抓取与需求相关的网页信息。聚焦网络爬虫极大地节省了硬件和网络资源,由于保存的页面数量少所以更新速度很快,这也很好地满足一些特定人群对特定领域信息的需求。
-
增量式网络爬虫:
是指对已下载网页采取增量式更新,它是一种只爬取新产生的或者已经发生变化网页的爬虫程序,能够在一定程度上保证所爬取的页面是最新的页面。
2)爬虫应用
随着网络的迅速发展,万维网成为大量信息的载体,如何有效地提取并利用这些信息成为一个巨大的挑战,因此爬虫应运而生,它不仅能够被使用在搜索引擎领域,而且在大数据分析,以及商业领域都得到了大规模的应用。
-
数据分析:在数据分析领域,网络爬虫通常是搜集海量数据的必备工具。对于数据分析师而言,要进行数据分析,首先要有数据源,而学习爬虫,就可以获取更多的数据源。在采集过程中,数据分析师可以按照自己目的去采集更有价值的数据,而过滤掉那些无效的数据。
-
商业领域:对于企业而言,及时地获取市场动态、产品信息至关重要。企业可以通过第三方平台购买数据,比如贵阳大数据交易所、数据堂等,当然如果贵公司有一个爬虫工程师的话,就可通过爬虫的方式取得想要的信息。
2、Python编写爬虫的流程
Python 语言支持多个爬虫模块,比如 urllib
、requests
、Bs4
等。Python 的请求模块和解析模块丰富成熟,并且还提供了强大的 Scrapy
框架,让编写爬虫程序变得更为简单。
1)编写爬虫的基础流程
爬虫程序与其他程序不同,它的的思维逻辑一般都是相似的, 所以无需我们在逻辑方面花费大量的时间。下面对 Python 编写爬虫程序的流程做简单地说明:
- 先由
urllib
模块的request
方法打开URL
得到网页HTML
对象。 - 使用浏览器打开网页源代码分析网页结构以及元素节点。
- 通过
Beautiful Soup
或者正则表达式提取数据。 - 存储数据到本地磁盘或数据库。
当然也不局限于上述一种流程。编写爬虫程序,需要您具备较好的 Python 编程功底,这样在编写的过程中您才会得心应手。爬虫程序需要尽量伪装成人访问网站的样子,而非机器访问,否则就会被网站的反爬策略限制,甚至直接封杀 IP,相关知识会在后续内容介绍。
2)第一个Python爬虫程序
a)获取响应对象
向百度(http://www.baidu.com/)发起请求,获取百度首页的 HTML 信息,代码如下:
#导包,发起请求使用urllib库的request请求模块
import urllib.request
# urlopen() 向URL发请求,返回响应对象,注意url必须完整
response = urllib.request.urlopen('http://www.baidu.com/')
print(response)
上述代码会返回百度首页的响应对象, 其中 urlopen()
表示打开一个网页地址。注意:请求的url
必须带有http
或者https
传输协议。
输出结果,如下所示:
<http.client.HTTPResponse object at 0x032F0F90>
b) 输出HTML信息
在上述代码的基础上继续编写如下代码:
#提取响应内容
html = response.read().decode('utf-8')
#打印响应内容
print(html)
输出结果如下,由于篇幅过长,此处只做了简单显示,可以看到解码格式为utf-8
:charset=utf-8"
:
<!DOCTYPE html><!--STATUS OK--> <html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="always" name="referrer"><meta name="theme-color" content="#2932e1"><meta name="description" content="全球最大的中文搜索引擎、致力于让网民更便捷地获取信息,找到...">...</html>
通过调用response
响应对象的 read()
方法提取 HTML
信息,该方法返回的结果是字节串类型(bytes),因此需要使用decode()
转换为字符串。程序完整的代码程序如下:
from urllib import request
#访问完整的url链接,返回响应对象
response = request.urlopen("http://www.baidu.com")
#通过响应对象获取html内容
html = response.read().decode("utf-8")
print(html)
通过上述代码获取了百度首页的 html
信息,这是最简单、最初级的爬虫程序。后续我们还学习如何分析网页结构、解析网页数据,以及存储数据等。
二、Python爬虫常用库
1、urllib基础介绍
参考
-
Python urllib | 菜鸟教程
-
网络爬虫urllib库常用函数解析
Python urllib
库是Python内置库,用于获取网页HTML
信息, 并对网页的内容进行抓取处理。
urllib
库包含以下几个模块:
-
urllib.request
:打开和读取 URL。 -
urllib.error
:包含 urllib.request 抛出的异常。 -
urllib.parse
:解析 URL。 -
urllib.robotparser
:解析robots.txt
文件。
1)urllib.request 模拟浏览器发起请求
urllib.request
定义了一些打开 URL 的函数和类,包含授权验证、重定向、浏览器 cookies等。urllib.request
可以模拟浏览器的一个请求发起过程。
a)urlopen发起URL请求
我们可以使用urllib.request
的urlopen
方法来打开一个URL
,语法格式如下:
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
- url:url 地址。
- data:发送到服务器的其他数据对象,默认为 None。
- timeout:设置访问超时时间。
- cafile 和 capath:cafile 为 CA 证书, capath 为 CA 证书的路径,使用 HTTPS 需要用到。
- cadefault:已经被弃用。
- context:ssl.SSLContext类型,用来指定 SSL 设置。
实例如下:
from urllib.request import urlopen
myURL = urlopen("https://www.runoob.com/")
print(myURL.read()) #二进制的html内容
---
b'<!Doctype html>\n<html>\n<head>\n\t<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n\t<meta name="viewport" content="width=device-width, initial-scale=1.0" />\n\t<title>\xe8\x8f\x9c\xe9\xb8\x9f\xe6\x95\x99\xe7\xa8\x8b - \xe5\xad\xa6\xe7\x9a\x84\xe4\xb8\x8d\xe4\xbb\x85\xe6\x98\xaf\xe6\x8a\x80\xe6\x9c\xaf\xef\xbc\x8c\xe6\x9b\xb4\xe6\x98\xaf\xe6\xa2\xa6\xe6\x83\xb3\xef\xbc\x81</title>\n\n <meta name=\'robots\' content=\'max-image-preview:large\' />\n<link rel=\'stylesheet\'
...
print(myURL.read().decode("utf-8")) #utf-8格式的html内容
---
<!Doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>菜鸟教程 - 学的不仅是技术,更是梦想!</title>
...
以上代码使用 urlopen
打开一个 URL,然后使用read()
函数获取网页的 HTML 实体代码。read()
是读取整个网页内容,也可以指定读取的长度:
from urllib.request import urlopen
myURL = urlopen("https://www.runoob.com/")
print(myURL.read(50).decode("utf-8")) #utf-8格式的html内容
---
<!Doctype html>
<html>
<head>
<meta http-equiv="C
b)request.Request模拟浏览器携带header信息
Http
请求的header
中可以携带请求端能够处理的压缩编码类型、浏览器和服务器端的连接类型(keepAlive
表示传输HTTP数据建立的TCP连接/close
表示TCP连接关闭)、操作系统和浏览器名称及版本等,参考浅谈Http请求中header的作用 - 简书
我们抓取网页一般需要对 headers(网页头信息)进行模拟,这时候需要使用到 urllib.request.Request
类:
urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
- url:url 地址。
- data:发送到服务器的其他数据对象,默认为 None。
- headers:HTTP 请求的头部信息,字典格式。
- origin_req_host:请求的主机地址,IP 或域名。
- unverifiable:很少用整个参数,用于设置网页是否需要验证,默认是False。。
- method:请求方法, 如 GET、POST、DELETE、PUT等。
有些网站在处理http
请求时,如果发现未携带header信息会请求失败:
import urllib.request
url = "https://movie.douban.com/"
resp = urllib.request.urlopen(url)
# 从响应结果中读取数据
html = resp.read().decode("utf-8")
print(html)
---
...
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 418:
主要原因是豆瓣服务器做了反爬虫措施,拒绝非浏览器的访问,检测到爬虫就返回一个 418 响应。参考爬取豆瓣电影top250 - 状态码 418 的解决方案 - 简书
解决方法如下实例:
import urllib.request
url = "https://movie.douban.com/"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36'}
req = Request(url,headers=headers)
resp = urllib.request.urlopen(req)
# 从响应结果中读取数据
html = resp.read().decode("utf-8")
print(html)
c)解决urllib无法获取动态网页的问题(使用selenium)
参考
-
Python爬虫:为什么你爬取不到网页数据
-
Python爬虫:爬取动态网页数据“你”需要知道的事
-
selenium和bs4的联合使用
先看看下面这段代码的执行结果:
from urllib import request
from urllib.request import Request
up_mainPage_url = 'https://space.bilibili.com/1641484091/?spm_id_from=333.999.0.0'
# 添加请求头
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4507.400'
}
req = Request(up_mainPage_url, headers=headers) # 如果不添加headers无法正常访问b站完整的html内容
response = request.urlopen(req)
html_byte = response.read() # 通过响应对象获取html内容
out_file = open('temp001.html','wb+')
out_file.write(html_byte)
out_file.close()
可以看到urllib
只能抓取静态网页的内容,对于ajax
异步渲染的完整html
页面并不能获取得到,这里可以通过selenium
来实现(selenium
的使用教程参考第3小节)
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
up_mainPage_url = 'https://space.bilibili.com/1641484091/?spm_id_from=333.999.0.0'# 加载动态网页
option = ChromeOptions()
# 设置浏览器为无头模式,使用过程中不会弹出浏览器页面
option.headless = True
driver = Chrome(service=ChromeService(ChromeDriverManager().install()), options=option) # 自动更新chrome驱动
driver.get(up_mainPage_url)
driver.implicitly_wait(10)
html_byte = driver.page_source.encode(encoding='utf-8')
out_file = open('temp002.html', 'wb+')
out_file.write(html_byte)
out_file.close()
可以看到这两个文件大小相差很大,后者获取的是完整的html:
2)urllib.error 异常请求处理
urllib.error
模块为 urllib.request
所引发的异常定义了异常类,基础异常类是 URLError
。
urllib.error
包含了两个方法,URLError
和HTTPError
。
-
URLError
是OSError
的一个子类,用于处理程序在遇到问题时会引发此异常(或其派生的异常),包含的属性reason
为引发异常的原因。 -
HTTPError
是URLError
的一个子类,用于处理特殊HTTP
错误例如作为认证请求的时候,包含的属性code
为 HTTP 的状态码,reason
为引发异常的原因,headers
为导致 HTTPError 的特定 HTTP 请求的 HTTP 响应头。
对不存在的网页抓取并处理异常,实例如下:
from urllib.request import urlopen
from urllib import error
myURL = urlopen("https://www.runoob.com/")
print(f"myURL status: {myURL.getcode()}") # 获取HTTP状态码
#请求异常捕获
try:
noURL = urlopen("https://www.runoob.com/no.html")
except error.HTTPError as e :
if e.code == 404:
print("noURL status: 404")
---
myURL status: 200
noURL status: 404
3)urllib.parse 解析URL
a)urlparse 解析URL属性
urllib.parse.urlparse
用于解析URL,格式如下:
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
-
urlstring
为字符串的url
地址,scheme
为协议类型, -
allow_fragments
参数为false
,则无法识别片段标识符。相反,它们被解析为路径,参数或查询组件的一部分,并 fragment 在返回值中设置为空字符串。实例如下:
from urllib.parse import urlparse
# 获取url的解析结果对象,包含10个属性:(scheme,netloc,pth,params,query,fragment,username,password,hostname,port)
parse_result = urlparse("https://www.bilibili.com/video/BV1XY41117Zq/?spm_id_from=333.1007.tianma.4-2-12.click&vd_source=c15f1a3e96ac60bc285f138ab7aeaa1c")
print(parse_result)
---
ParseResult(scheme='https', netloc='www.bilibili.com', path='/video/BV1XY41117Zq/', params='', query='spm_id_from=333.1007.tianma.4-2-12.click&vd_source=c15f1a3e96ac60bc285f138ab7aeaa1c', fragment='')
从结果可以看出,内容是一个元组,包含 6 个字符串:协议,位置,路径,参数,查询,判断。我们可以直接读取协议内容,实例如下:
from urllib.parse import urlparse
parse_result = urlparse("https://www.bilibili.com/video/BV1XY41117Zq/?spm_id_from=333.1007.tianma.4-2-12.click&vd_source=c15f1a3e96ac60bc285f138ab7aeaa1c")
print(f"scheme: {parse_result.scheme}, netloc: {parse_result.netloc}, "
f"pth: {parse_result.path}, params: {parse_result.params}, "
f"query: {parse_result.query}, fragment: {parse_result.fragment}, "
f"username: {parse_result.username}, password: {parse_result.password}, "
f"hostname: {parse_result.hostname}, port: {parse_result.port}")
---
scheme: https, netloc: www.bilibili.com, pth: /video/BV1XY41117Zq/, params: , query: spm_id_from=333.1007.tianma.4-2-12.click&vd_source=c15f1a3e96ac60bc285f138ab7aeaa1c, fragment: , username: None, password: None, hostname: www.bilibili.com, port: None
完整内容如下:
属性 | 索引 | 值 | 值(如果不存在) |
---|---|---|---|
scheme |
0 | URL协议 |
scheme 参数 |
netloc |
1 | 网络位置部分 | 空字符串 |
path |
2 | 分层路径 | 空字符串 |
params |
3 | 最后路径元素的参数 | 空字符串 |
query |
4 | 查询组件 | 空字符串 |
fragment |
5 | 片段识别 | 空字符串 |
username |
用户名 | None |
|
password |
密码 | None |
|
hostname |
主机名(小写) | None |
|
port |
端口号为整数(如果存在) | None |
b)urlencode 对传参的请求编码 & unquote对请求解码
from urllib.request import Request
from urllib.parse import urlencode
from urllib.parse import unquote
url = 'https://search.bilibili.com/all?' # bilibili搜索页面
dic = {'keyword': '氰化物欢乐秀合集 p1'}
full_url = url + urlencode(dic) # 对请求进行编码
print(full_url)
print(unquote(full_url)) # 对请求进行解码
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36'
} # 头部信息
request = Request(full_url, headers=header)
reponse = urlopen(request).read()
print(reponse)
fh = open("./urllib_test_bilibili_search.html", "wb") # 以二进制的形式将文件写入到当前目录中
fh.write(reponse)
fh.close()
---
https://search.bilibili.com/all?keyword=%E6%B0%B0%E5%8C%96%E7%89%A9%E6%AC%A2%E4%B9%90%E7%A7%80%E5%90%88%E9%9B%86+p1
https://search.bilibili.com/all?keyword=氰化物欢乐秀合集+p1
b'<!DOCTYPE html>\n<html lang="zh-CN">\n <head>\n <meta charset="UTF-8" />\n \n <title>\xe6\xb0\xb0\xe5\x8c\x96\xe7\x89\xa9\xe6\xac\xa2\xe4\xb9\x90\xe7\xa7\x80\xe5\x90\x88\xe9\x9b\x86 p1-\xe5\x93\x94\xe5\x93\xa9\xe5\x93\x94\xe5\x93\xa9_Bilibili</title>\n <meta name="description" content="bilibili\xe6\x98\xaf\xe5\x9b\xbd\xe5\x86\x85\xe7\x9f\xa5\xe5\x90\x8d\xe7\x9a\x84\xe8\xa7\x86\xe9\xa2\x91\xe5\xbc\xb9\xe5\xb9\x95\xe7\xbd\x91
...
4)urllib.robotparser 解析robots.txt
文件规则
参考【爬虫篇】根据网站的robots.txt文件判断一个爬虫是否有权限爬取这个网页
urllib.robotparser
用于解析robots.txt
文件。
robots.txt
(统一小写)是一种存放于网站根目录下的 robots 协议,它通常用于告诉搜索引擎对网站的抓取规则。urllib.robotparser
提供了 RobotFileParser
类,语法如下:
urllib.robotparser.RobotFileParser(url='')
这个类提供了一些可以读取、解析robots.txt
文件的方法:
-
set_url(url)
: 设置robots.txt
文件的 URL。 -
read()
: 读取robots.txt
URL 并将其输入解析器。 -
parse(lines)
: 解析行参数。 -
can_fetch(useragent, url)
: 如果允许useragent
按照被解析robots.txt
文件中的规则来获取url
则返回 True。 -
mtime()
: 返回最近一次获取robots.txt
文件的时间。 这适用于需要定期检查robots.txt
文件更新情况的长时间运行的网页爬虫。 -
modified()
: 将最近一次获取robots.txt
文件的时间设置为当前时间。 -
crawl_delay(useragent)
: 为指定的 useragent 从 robots.txt 返回 Crawl-delay 形参。 如果此形参不存在或不适用于指定的 useragent 或者此形参的 robots.txt 条目存在语法错误,则返回 None。 -
request_rate(useragent)
: 以named tuple RequestRate(requests, seconds)
的形式从robots.txt
返回 Request-rate 形参的内容。 如果此形参不存在或不适用于指定的 useragent 或者此形参的 robots.txt 条目存在语法错误,则返回 None。 -
site_maps()
: 以list()
的形式从 robots.txt 返回 Sitemap 形参的内容。 如果此形参不存在或者此形参的robots.txt
条目存在语法错误,则返回 None。
https://www.baidu.com/robots.txt 的内容如下:
User-agent: Baiduspider Disallow: /baidu Disallow: /s? Disallow: /ulink? Disallow: /link? Disallow: /home/news/data/ Disallow: /bh
User-agent: Googlebot Disallow: /baidu Disallow: /s? Disallow: /shifen/ Disallow: /homepage/ Disallow: /cpro Disallow: /ulink? Disallow: /link? Disallow: /home/news/data/ Disallow: /bh
...
https://www.bilibili.com/robots.txt 的内容如下:
User-agent: Yisouspider Allow: /
User-agent: Applebot Allow: /
User-agent: bingbot Allow: /
User-agent: Sogou inst spider Allow: /
User-agent: Sogou web spider Allow: /
User-agent: 360Spider Allow: /
User-agent: Googlebot Allow: /
User-agent: Baiduspider Allow: /
User-agent: Bytespider Allow: /
User-agent: PetalBot Allow: /
User-agent: * Disallow: /
a)实例1:解析baidu的robots
协议文件
from urllib import robotparser
# 参考 https://blog.csdn.net/weixin_40458518/article/details/122785687
#获取robotParser解析对象
robotp = robotparser.RobotFileParser("https://www.baidu.com/robots.txt")
#用来解析robots.txt文件并进行分析:该语句不能缺失,否则can_fetch方法中全都判断为False
robotp.read()
# 允许百度爬虫访问 域名的根目录
print(robotp.can_fetch('Baiduspider', 'https://www.baidu.com')) # True
# 允许百度爬虫访问 目录/homepage/
print(robotp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/')) # True
# 禁止谷歌爬虫访问 目录/homepage/
print(robotp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/')) # False
print(robotp.can_fetch('Googlebot', 'https://www.baidu.com')) # True
# 爬虫名称为空或在robots协议中找不到,就匹配"User-agent: *",禁止所有爬虫访问所有目录
print(robotp.can_fetch('', 'https://www.baidu.com')) # False
b)实例2:解析bilibili的robots
协议文件
from urllib import robotparser
# 参考 https://blog.csdn.net/weixin_40458518/article/details/122785687
#获取robotParser解析对象
robotp = robotparser.RobotFileParser("https://www.bilibili.com/robots.txt")
#用来解析robots.txt文件并进行分析:该语句不能缺失,否则can_fetch方法中全都判断为False
robotp.read()
# 允许百度爬虫访问 域名的根目录
print(robotp.can_fetch('Baiduspider', 'https://www.bilibili.com')) # True
# 允许百度爬虫访问 目录/homepage/
print(robotp.can_fetch('Baiduspider', 'https://www.bilibili.com/')) # True
# 禁止谷歌爬虫访问 目录/homepage/
print(robotp.can_fetch('Googlebot', 'https://www.bilibili.com/')) # True
# 爬虫名称为空或在robots协议中找不到,就匹配"User-agent: *",禁止所有爬虫访问所有目录
print(robotp.can_fetch('', 'https://www.bilibili.com/')) # False
c)实例3:urllib.can_fetch()
作为条件判断
from urllib import robotparser
from urllib.request import Request
from urllib.parse import urlencode
from urllib.parse import unquote
rp = robotparser.RobotFileParser("https://www.bilibili.com/robots.txt")
rp.read()
dic = {'keyword': '间谍过家家'}
url = 'https://search.bilibili.com/bangumi?' + urlencode(dic) #番剧
url2 = 'https://search.bilibili.com/article?' + urlencode(dic) #专栏
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.80 Safari/537.36'
}
if rp.can_fetch('Baiduspider', url):
print(f'正在爬取网站:{unquote(url)}') #解码后的url
res = Request(url=url, headers=headers)
print(res)
if rp.can_fetch('Baiduspider', url2):
print(f'正在爬取网站:{unquote(url2)}') #解码后的url
res = Request(url=url2, headers=headers)
print(res)
else:
print('网站不允许任何爬虫,爬取网站的内容。')
---
正在爬取网站:https://search.bilibili.com/bangumi?keyword=间谍过家家
<urllib.request.Request object at 0x0000020DC0D2CC40>
正在爬取网站:https://search.bilibili.com/article?keyword=间谍过家家
<urllib.request.Request object at 0x0000020DC0D2CC70>
5)IP代理避免反爬虫
参考Python3网络爬虫(四):使用User Agent和代理IP隐藏身份
2、Bs4基础介绍
参考Beautiful Soup 4 入门手册_w3cschool
1)soup文档对象 及 对象类型
a)初始化soup
文档对象
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("urllib_test_bilibili_search.html",encoding="utf-8"))
print(soup)
print("*********************************")
soup = BeautifulSoup("<html>data</html>")
print(soup) # <html><body><p>data</p></body></html>
输出结果:
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<title>氰化物欢乐秀合集 p1-哔哩哔哩_Bilibili</title>
<meta content="bilibili是国内知名的视频弹幕网站,这里有及时的动漫新番,活跃的ACG氛围,有创意的Up主。大家可以在这里找到许多欢乐。" name="description"/>
<meta content="B站,弹幕,字幕,AMV,MAD,MTV,ANIME,动漫,动漫音乐,游戏,游戏解说,ACG,galgame,动画,番组,新番,初音,洛天依,vocaloid" name="keywords"/>
...
*********************************
<html><body><p>data</p></body></html>
b)Tag
标签对象类型
参考Beautiful Soup 4 对象类型_w3cschool
Beautiful Soup
将复杂HTML
文档转换成一个复杂的树形结构, 每个节点都是Python
对象,所有对象可以归纳为4种:Tag
, NavigableString
, BeautifulSoup
, Comment
。
-
获取标签内容即类型:
'''Tag类型: tag能够包含字符串或是其它tag''' soup = BeautifulSoup('<body>' '<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div> ' '</body>') tag = soup.b #获取文档对象中的b标签 print(f"tag标签内容:{tag},b标签的类型={type(tag)}") 输出结果 --- tag标签内容:<b class="boldest strikeout" style="color:red">Extremely bold</b>,b标签的类型=<class 'bs4.element.Tag'>
-
获取标签下的其他对象类型:
'''Tag类型: tag能够包含字符串或是其它tag''' soup = BeautifulSoup('<body>' '<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div> ' '</body>') '''获取Tag标签下的其他tag''' print(f"<body>标签下的其他tag:{soup.body.contents}") print(f"<body>标签下的其他tag:{soup.body.tag}",end="\n\n") 输出结果 --- <body>标签下的其他tag:[<b class="boldest strikeout" style="color:red">Extremely bold</b>, ' ', <b class="lightest strikeout" style="color:black">Extremely light</b>, ' ', <div class="medium strikeout" style="color:blue">medium</div>, ' '] <body>标签下的其他tag:None
-
获取标签的
name
属性:'''Tag类型: tag能够包含字符串或是其它tag''' soup = BeautifulSoup('<body>' '<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div> ' '</body>') tag = soup.b #获取文档对象中的b标签 '''Tag的name属性: 获取Tag标签名称''' #1. 通过Tag.name获取标签的名字 print(f"origin tag.name: {tag.name},tag标签内容:{tag}") #2. 通过对Tag.name赋值修改标签名字 tag.name = "helloWorld" print(f"update tag.name: {tag.name},tag标签内容:{tag}",end="\n\n") 输出结果 --- origin tag.name: b,tag标签内容:<b class="boldest strikeout" style="color:red">Extremely bold</b> update tag.name: helloWorld,tag标签内容:<helloWorld class="boldest strikeout" style="color:red">Extremely bold</helloWorld>
-
获取标签的属性(单个属性/多个属性):
'''Tag类型: tag能够包含字符串或是其它tag''' soup = BeautifulSoup('<body>' '<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div> ' '</body>') tag = soup.b #获取文档对象中的b标签 '''Tag的attrs:获取Tag标签的属性''' #1. 获取Tag标签的所有属性 print(f"tag.attrs:{tag.attrs}") #只获取第一次出现该标签的所有属性 #2. 获取Tag标签的单个属性 print(f"tag['class']:{tag['class']}") #只获取第一次出现该标签的class属性 #3. 修改标签的class属性 tag["class"] = ["helloWorld","HeySiri"] print(f"tag['class']:{tag['class']}",end="\n\n") 输出结果 --- tag.attrs:{'class': ['boldest', 'strikeout'], 'style': 'color:red'} tag['class']:['boldest', 'strikeout'] tag['class']:['helloWorld', 'HeySiri']
-
修改标签属性内容、 删除标签属性:
'''Tag类型: tag能够包含字符串或是其它tag''' soup = BeautifulSoup('<body>' '<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div> ' '</body>') tag = soup.b #获取文档对象中的b标签 #4. 删除标签的class属性 del tag["class"] print(f"tag内容:{tag}",end="\n\n") # 添加并修改标签的class属性:会报错`return self.attrs[key] KeyError`: 'class',因为之前删除了该标签的class属性 # tag["class"] = "helloWorld" # print(f"tag['class']:{tag['class']}") 输出结果 --- tag内容:<helloWorld style="color:red">Extremely bold</helloWorld>
c)NavigableString
字符串对象类型
字符串常被包含在tag
内。Beautiful Soup
用 NavigableString
类来包装tag
中的字符串(参考Beautiful Soup 4 可遍历的字符串_w3cschool):
-
获取
NavigableString
内容即类型:soup = BeautifulSoup('<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div>') tag = soup.b # 获取文档对象中的b标签 unicode_string = tag.string #通过unicode()方法可以直接将 NavigableString 对象转换成Unicode字符串 print(f"tag标签内容:{tag},b标签的类型={type(tag)}") print(f"unicode_string内容:{unicode_string},unicode_string的类型={type(unicode_string)}",end="\n\n") 输出结果 --- tag标签内容:<b class="boldest strikeout" style="color:red">Extremely bold</b>,b标签的类型=<class 'bs4.element.Tag'> unicode_string内容:Extremely bold,unicode_string的类型=<class 'bs4.element.NavigableString'>
-
修改标签的
string
文本属性(两种修改方式):soup = BeautifulSoup('<b class="boldest strikeout" style="color:red">Extremely bold</b> ' '<b class="lightest strikeout" style="color:black">Extremely light</b> ' '<div class="medium strikeout" style="color:blue">medium</div>') tag = soup.b # 获取文档对象中的b标签 tag.string = "Helloworld" print(f"tag标签内容:{tag},unicode_string内容:{tag.string}") tag.string.replace_with("fuckWorld") print(f"tag标签内容:{tag},unicode_string内容:{tag.string}") 输出结果 --- tag标签内容:<b class="boldest strikeout" style="color:red">Helloworld</b>, unicode_string内容:Helloworld tag标签内容:<b class="boldest strikeout" style="color:red">fuckWorld</b>,unicode_string内容:fuckWorld
d)Comment
注释类型(prettify()
对标签内容进行美化)
参考Beautiful Soup 4 注释怎么写_w3cschool
-
获取注释内容即类型:
Comment
其实是NavigableString
的子类markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup) comment = soup.b.string print(f"b标签的注释内容:{comment},注释类型={type(comment)}") #bs4.element.Comment其实是NavigableString的子类 输出结果 --- b标签的注释内容:Hey, buddy. Want to buy a used parser?,注释类型=<class 'bs4.element.Comment'>
-
注释美化和注释修改:
Beautiful Soup
中定义的其它类型都可能会出现在XML的文档中:CData
,ProcessingInstruction
,Declaration
,Doctype
.与Comment
对象类似,这些类都是NavigableString
的子类markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup) comment = soup.b.string '''注释美化''' print(f"注释美化: {soup.b.prettify()}") '''注释修改''' # Beautiful Soup中定义的其它类型都可能会出现在XML的文档中: CData , ProcessingInstruction , Declaration, Doctype .与 Comment对象类似,这些类都是NavigableString的子类, # 只是添加了一些额外的方法的字符串独享.下面是用CDATA来替代注释的例子: cdata = CData("A CDATA block") comment.replace_with(cdata) print(f"b标签的注释内容:{comment}") 输出结果 --- 注释美化: <b> <!--Hey, buddy. Want to buy a used parser?--> </b> b标签的注释内容:Hey, buddy. Want to buy a used parser?
2)遍历
参考Beautiful Soup 4 遍历_w3cschool
a)子节点:contents
,children
和descendants
-
获取直接子节点(
.contents
和.children
):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" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well. </p> <p class="story">...</p> </body> </html> """ soup = BeautifulSoup(html_doc,"html.parser") '''tag的 .contents 属性可以将tag的子节点以列表的方式输出:''' head = soup.head print(f"输出head标签的子节点:{head.contents}, type(head.contents):{type(head.contents)}") title_tag = head.contents[0] print(f"title_tag : {title_tag}, type(title_tag) : {type(title_tag)}", end="\n\n") '''字符串没有.contents 属性, 因为字符串没有子节点''' title_str = title_tag.contents[0] print(f"title_str : {title_str}, type(title_str) : {type(title_str)}",end="\n\n") # print(f"title_str.contents : {title_str.contents}") #AttributeError: 'NavigableString' object has no attribute 'contents' '''通过tag的.children生成器,可以对tag的第一级子节点进行循环: 输出仅包含tag的直接子节点''' for index,child in enumerate(soup.children): print(f"{index} : {child}") print("**************************") for index,child in enumerate(soup.head.children): print(f"{index} : {child}") print("**************************") for index,child in enumerate(soup.body.children): print(f"{index} : {child}") 输出结果 --- 输出head标签的子节点:[<title>The Dormouse's story</title>], type(head.contents):<class 'list'> title_tag : <title>The Dormouse's story</title>, type(title_tag) : <class 'bs4.element.Tag'> title_str : The Dormouse's story, type(title_str) : <class 'bs4.element.NavigableString'> 0 : 1 : <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 class="sister" href="http://example.com/elsie" id="link1" rel="external nofollow" target="_blank">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2" rel="external nofollow" target="_blank">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3" rel="external nofollow" target="_blank">Tillie</a>; and they lived at the bottom of a well. </p> <p class="story">...</p> </body> </html> 2 : ************************** 0 : <title>The Dormouse's story</title> ************************** 0 : 1 : <p class="title"><b>The Dormouse's story</b></p> 2 : 3 : <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1" rel="external nofollow" target="_blank">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2" rel="external nofollow" target="_blank">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3" rel="external nofollow" target="_blank">Tillie</a>; and they lived at the bottom of a well. </p> 4 : 5 : <p class="story">...</p> 6 :
-
递归获取子孙节点(
.descendants
):''' .contents 和 .children 属性仅包含tag的直接子节点.例如,<head>标签只有一个直接子节点<title> 但是<title>标签也包含一个子节点:字符串 “The Dormouse’s story”, 这种情况下字符串 “The Dormouse’s story”也属于<head>标签的子孙节点。.descendants 属性可以对所有tag的子孙节点进行递归循环 ''' 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" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well. </p> <p class="story">...</p> </body> </html> """ soup = BeautifulSoup(html_doc, "html.parser") for index,child in enumerate(soup.descendants): print(f"{index} : {child}") print(f"len(soup.children):{len(list(soup.children))}, len(soup.descendants):{len(list(soup.descendants))}") 输出结果 --- ... #递归输出所有节点 len(soup.children):3, len(soup.descendants):30
-
获取子节点文本内容:
-
如果
tag
只有一个NavigableString
类型子节点,那么这个tag
可以使用.string
得到子节点;如果tag
包含了多个子节点,tag
就无法确定,.string
的输出结果是None
; -
如果
tag
中包含多个字符串,可以使用.strings
来循环获取;为了去除掉多余的空格内容,可以使用stripped_strings
;
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"> story </p> </body> </html> """ soup = BeautifulSoup(html_doc, "html.parser") #如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点: print(f"soup.html.head.title.string: {soup.html.head.title.string}") #如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None print(f"soup.html.string: {soup.html.string}",end="\n\n") # 如果tag中包含多个字符串,可以使用 .strings 来循环获取, # 其中输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容: # for index,string in enumerate(soup.strings): for index,string in enumerate(soup.stripped_strings): print(f"{index} : {string}") 输出结果: --- soup.html.head.title.string: The Dormouse's story soup.html.string: None 0 : The Dormouse's story 1 : The Dormouse's story 2 : story
-
b)父节点:parent
和parents
-
文档树中每个
tag
或字符串都有父节点:.parent
获取到的父节点可以是Tag
类型、BeautifulSoup
对象或者None
。'''文档树中每个tag或字符串都有父节点:被包含在某个tag中''' 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"> story </p> </body> </html> """ soup = BeautifulSoup(html_doc, "html.parser") title_tag = soup.html.head.title # 获取title标签的父标签 print(f"title_tag: {title_tag}, title_tag.parent: {title_tag.parent}") #文档title的字符串 NavigableString没有子节点,但有父节点:<title>标签 title_str = title_tag.string print(f"title_str: {title_str}, title_str.parent: {title_str.parent}",end="\n\n") #文档的顶层节点比如<html>的父节点是 BeautifulSoup 对象 html_tag = soup.html print(f"html_tag.parent: {html_tag.parent}, type(html_tag.parent): {type(html_tag.parent)}") # BeautifulSoup 对象的 .parent 是None: print(f"soup.parent: {soup.parent}") 输出结果 --- title_tag: <title>The Dormouse's story</title>, title_tag.parent: <head><title>The Dormouse's story</title></head> title_str: The Dormouse's story, title_str.parent: <title>The Dormouse's story</title> html_tag.parent: <html> <head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p class="story"> story </p> </body> </html> , type(html_tag.parent): <class 'bs4.BeautifulSoup'> soup.parent: None
-
通过
.parents
递归获取父节点:html_doc = """ <html> <head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p> <a class="sister" href="http://example.com/elsie" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" id="link1"> Elsie </a> </p> <p class="story"> story </p> </body> </html> """ soup = BeautifulSoup(html_doc, "html.parser") a_tag = soup.a #通过 .parents生成器,遍历<a>标签到根节点的所有节点. for index,parent in enumerate(a_tag.parents): if(parent == None): #到达根节点 print(f"{index},{parent}") else: print(f"{index},{parent.name}") 输出结果 --- 0,p 1,body 2,html 3,[document]
c)兄弟节点:next_sibling(s) & previous_sibling(s)
-
获取未美化
html
代码的兄弟节点sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c><d>text3</d></a>") print(f'标签内容美化:{sibling_soup.prettify()}') #其中<b>标签和<c>标签是同一层,及两者为兄弟节点 #在文档树中, 使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点 b_tag = sibling_soup.b c_tag = sibling_soup.c print(f"b_tag.next_sibling : {b_tag.next_sibling}, c_tag.next_sibling : {c_tag.next_sibling}") print(f"b_tag.previous_sibling : {b_tag.previous_sibling}, c_tag.previous_sibling : {c_tag.previous_sibling}") 输出结果 --- 标签内容美化:<html> <body> <a> <b> text1 </b> <c> text2 </c> <d> text3 </d> </a> </body> </html> b_tag.next_sibling : <c>text2</c>, c_tag.next_sibling : <d>text3</d> b_tag.previous_sibling : None, c_tag.previous_sibling : <b>text1</b>
-
获取美化
html
代码后的兄弟节点:实际文档中的tag的.next_sibling
和.previous_sibling
属性通常是字符串或空白,.next_sibling.next_sibling
才能获取到相应的兄弟节点。sibling_soup = BeautifulSoup('<a><b>text1</b><c>text2</c><d id="link3">text3</d></a>') prettify_soup = BeautifulSoup(sibling_soup.prettify()) # 实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白, .next_sibling.next_sibling才能获取到相应的兄弟节点 b_tag = prettify_soup.b c_tag = prettify_soup.c print(f"b_tag.next_sibling : {b_tag.next_sibling}, c_tag.next_sibling : {c_tag.next_sibling}") print(f"b_tag.next_sibling.next_sibling : {b_tag.next_sibling.next_sibling}, c_tag.next_sibling.next_sibling : {c_tag.next_sibling.next_sibling}") 输出结果 --- b_tag.next_sibling : , c_tag.next_sibling : b_tag.next_sibling.next_sibling : <c> text2 </c>, c_tag.next_sibling.next_sibling : <d id="link3"> text3 </d>
-
递归获取所有的兄弟节点(通过
soup.find(attrs={})
来获得指定属性内容的标签对象)sibling_soup = BeautifulSoup('<a><b>text1</b><c>text2</c><d id="link3" class="link3">text3</d></a>') prettify_soup = BeautifulSoup(sibling_soup.prettify()) # 通过 .next_siblings生成器 属性可以对当前节点的兄弟节点迭代输出: for index,next_sibling in enumerate(prettify_soup.next_siblings): print(f"{index},{next_sibling}") #通过 .previous_siblings生成器 属性可以对当前节点的兄弟节点迭代输出: # tag = prettify_soup.find(id="link3") #按id来查询标签 tag = prettify_soup.find(attrs={"class" : "link3"}) #按class来查询标签, 参考https://blog.csdn.net/weixin_30629653/article/details/94979800/ for index,previous_sibling in enumerate(tag.previous_siblings): print(f"{index},{previous_sibling}") 输出结果 --- tag : <d class="link3" id="link3"> text3 </d>, previous_tags: 0, 1,<c> text2 </c> 2, 3,<b> text1 </b> 4,
d)上/下一个HTML
解析对象:next_element(s) & previous_element(s)
HTML
解析器把这段字符串转换成一连串的事件:
- “打开
<html>
标签”, ”打开一个<head>
标签”, ”打开一个<title>
标签”, ”添加一段字符串”, ”关闭标签”, ”打开<code><p></code>标签”,等等。<code>Beautiful Soup</code>提供了<strong>重现解析器初始化过程</strong>的方法。
-
获取下一个/上一个解析对象
html_doc = '''<a><b>text1</b><c>text2</c><d id="link3">text3</d><e>text4</e></a>''' html_soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) #.next_element 属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling 相同,但通常是不一样的 d_tag = html_soup.find(attrs={"id":"link3"}) print(f"d_tag.next_sibling:{d_tag.next_sibling}, d_tag.next_element:{d_tag.next_element}") print(f"d_tag.previous_sibling:{d_tag.previous_sibling}, d_tag.next_element:{d_tag.previous_element}") 输出结果 --- d_tag.next_sibling: , d_tag.next_element: text3 d_tag.previous_sibling: , d_tag.next_element:
-
递归获取下一个/上一个解析对象
html_doc = '''<a><b>text1</b><c>text2</c><d id="link3">text3</d><e>text4</e></a>''' html_soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) d_tag = html_soup.find(attrs={"id": "link3"}) print(f"d_tag : {d_tag}, previous_elements: ") for index, previous_element in enumerate(d_tag.previous_elements): print(f"{index},{previous_element}") 输出结果 --- d_tag : <d id="link3"> text3 </d>, previous_elements: 0, 1, text2 2,<c> text2 </c> 3, 4, text1 5,<b> text1 </b> 6, 7,<a> <b> text1 </b> <c> text2 </c> <d id="link3"> text3 </d> <e> text4 </e> </a> 8, 9,<body> <a> <b> text1 </b> <c> text2 </c> <d id="link3"> text3 </d> <e> text4 </e> </a> </body> 10, 11,<html> <body> <a> <b> text1 </b> <c> text2 </c> <d id="link3"> text3 </d> <e> text4 </e> </a> </body> </html>
3)文档搜索
BeautifulSoup
搜索文档主要有两个方法:find
& find_all
,参考Beautiful Soup 4 搜索文档_w3cschool
a)find_all()
搜索子孙节点(find
等价于find_all(limit=1)
)
find_all
参数细节:find_all(name , attrs , recursive , string , **kwargs )
-
name
参数:搜索name
参数的值可以使任一类型的 过滤器,字符串,正则表达式,列表,方法或是True
。''' find_all方法 ''' html_doc = """<a><b>text1</b><c class="hello">text2</c><d class="world" id="link3">text3</d><e>text4</e></a>""" soup = BeautifulSoup(html_doc) '''Step1: 初步使用find_all''' # 1.参数为字符串 print(f'soup.find_all("b") : {soup.find_all("b")}',end="\n\n") # 2.参数为正则表达式 import re for index,tag in enumerate(soup.find_all(re.compile("^b"))): #获取以b开头的所有标签 print(f"以b开头的标签{index}:{tag.name}") print(end="\n\n") # 3.参数为列表 print(f'find_all(["a", "b"]) : {soup.find_all(["a", "b"])}',end="\n\n") # 4.参数为布尔值(输出所有标签) for index,tag in enumerate(soup.find_all(True)): print(f"{index},{tag.name}") print(end="\n\n") # 5.参数为方法,自定义过滤器 ''' 如果没有合适过滤器,那么还可以定义一个方法: 方法只接受一个元素Tag参数,如果这个方法返回 True 表示当前元素Tag匹配并且被找到,如果不是则返回False ''' def has_class_but_no_id(tag): # 方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True return tag.has_attr('class') and not tag.has_attr('id') print(f"使用has_class_but_no_id作为find_all参数: {soup.find_all(has_class_but_no_id)}",end="\n\n") def surrounded_by_strings(tag): #过滤出在解析过程中前后都有文字的标签 return (isinstance(tag.next_element, NavigableString) and isinstance(tag.previous_element, NavigableString)) print(f"使用surrounded_by_strings作为find_all参数: {soup.find_all(surrounded_by_strings)}", end="\n\n") 输出结果 --- soup.find_all("b") : [<b>text1</b>] 以b开头的标签0:body 以b开头的标签1:b find_all(["a", "b"]) : [<a><b>text1</b><c class="hello">text2</c><d class="world" id="link3">text3</d><e>text4</e></a>, <b>text1</b>] 0,html 1,body 2,a 3,b 4,c 5,d 6,e 使用has_class_but_no_id作为find_all参数: [<c class="hello">text2</c>] 使用surrounded_by_strings作为find_all参数: [<c class="hello">text2</c>, <d class="world" id="link3">text3</d>, <e>text4</e>]
-
keyword
参数:使用多个指定名字的参数可以同时过滤tag
的多个属性, 通过class_
参数搜索有指定CSS
类名的tag
import re html_doc = """ <a><b>text1</b><c class="hello"><div>text2</div></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # 2. keyword 参数: 使用多个指定名字的参数可以同时过滤tag的多个属性, 通过 class_ 参数搜索有指定CSS类名的tag print(f'soup.find_all(class_=re.compile("or"), id="link3"): {soup.find_all(class_=re.compile("or"), id="link3")}') # 等价于 使用attrs字典参数 print(f'soup.find_all(attrs=("class": re.compile("or"), "id" : "link3")): {soup.find_all(attrs={"class": re.compile("or"), "id" : "link3"})}') # 等价于 省略掉find_all方法 print(f'soup(class_=re.compile("or"), id="link3"): {soup(class_=re.compile("or"), id="link3")}',end="\n\n") # 有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性: # print(f'soup.find_all(data-foo="value"): {soup.find_all(data-foo="value")}') # 按CSS搜索: 按照CSS类名搜索tag时, 可以分别搜索tag中的每个CSS类名 print(f'soup.find_all("c", class_="hello")"):{soup.find_all("c", class_="hello")}',end="\n\n") 输出结果: --- soup.find_all(class_=re.compile("or"), id="link3"): [<c class="world" id="link3"><div>text3</div></c>] soup.find_all(attrs=("class": re.compile("or"), "id" : "link3")): [<c class="world" id="link3"><div>text3</div></c>] soup(class_=re.compile("or"), id="link3"): [<c class="world" id="link3"><div>text3</div></c>] soup.find_all("c", class_="hello")"):[<c class="hello"><div>text2</div></c>]
-
string
参数:通过string
参数可以搜搜文档中的字符串内容, 见NavigableString
小节。import re html_doc = """ <a><b>text1</b><c class="hello"><div>text2</div></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) print(f'soup.find_all(string="text1") : {soup.find_all(string="text1")}') print(f'soup.find_all(string=["text1","text2"]) : {soup.find_all(string=["text1","text2"])}',end="\n\n") 输出结果: --- soup.find_all(string="text1") : ['text1'] soup.find_all(string=["text1","text2"]) : ['text1', 'text2']
-
recursive
参数:调用tag
的find_all()
方法时,Beautiful Soup会检索当前tag的所有子孙节点, 如果只想搜索tag
的直接子节点,可以使用参数recursive=False
(???) 。import re html_doc = """ <a><b>text1</b><c class="hello"><div>text2</div></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) print(f'soup.html.find_all("c",recursive=True): {soup.html.find_all("c",recursive=True)}') print(f'soup.html.find_all("c",recursive=False): {soup.html.find_all("c",recursive=False)}',end="\n\n") #返回[], 不知道recursive=False有什么用 输出结果: --- soup.html.find_all("c",recursive=True): [<c class="hello"><div>text2</div></c>, <c class="world" id="link3"><div>text3</div></c>] soup.html.find_all("c",recursive=False): []
-
limit
参数:一段简单的文档: 当搜索到的结果数量达到limit
的限制时,就停止搜索返回结果,效果与SQL中的limit
关键字类似。 使用find_all
方法并设置
limit=1
参数不如直接使用find()
`方法,下面这两行代码是等价的。import re html_doc = """ <a><b>text1</b><c class="hello"><div>text2</div></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) print(f'soup.find_all("c", limit=1) : {soup.find_all("c", limit=1)}') #find_all limit=1等价于使用find print(f'soup.find("c") : {soup.find("c")}', end="\n\n") 输出结果: --- soup.find_all("c", limit=1) : [<c class="hello"><div>text2</div></c>] soup.find("c") : <c class="hello"><div>text2</div></c>
-
find_all()
可省略:import re html_doc = """ <a><b>text1</b><c class="hello"><div>text2</div></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) print(f'soup.find_all("c") : {soup.find_all("c")}') print(f'soup("c") : {soup("c")}') 输出结果: --- soup.find_all("c") : [<c class="hello"><div>text2</div></c>, <c class="world" id="link3"><div>text3</div></c>] soup("c") : [<c class="hello"><div>text2</div></c>, <c class="world" id="link3"><div>text3</div></c>]
b)其他find_xxx()
方法
-
find_parents()
和find_parent()
: 通过.parents
属性对当前tag
父辈节点的tag
节点进行迭代,搜索方法的参数使用同find_all
。html_doc = """ <a><b>text1</b><c><c class="hello"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) div_tag = soup.c.div '''find_parents() 和 find_parent(): 通过 `.parents` 属性对当前`tag`父辈节点的`tag`节点进行迭代''' print(f'div_tag.find_parent(): {div_tag.find_parent("c")}') print(f'div_tag.find_parents(): {div_tag.find_parents("c")}',end="\n\n") 输出结果 --- div_tag.find_parent(): <c class="hello"><div>text2</div></c> div_tag.find_parents(): [<c class="hello"><div>text2</div></c>, <c><c class="hello"><div>text2</div></c></c>]
-
find_next_siblings()
和find_next_sibling()
:通过.next_siblings
属性对当前tag
的后面兄弟tag
节点进行迭代, 搜索当前节点后面的兄弟节点,搜索方法的参数使用同find_all
。html_doc = """ <a><b>text1</b><c><c class="hello"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) div_tag = soup.c.div '''find_next_siblings() 合 find_next_sibling(): 通过 .next_siblings 属性对当前tag的后面兄弟tag节点进行迭代''' b_tag = soup.b print(f'b_tag.find_next_sibling(): {b_tag.find_next_sibling("c")}') print(f'b_tag.find_next_siblings(): {b_tag.find_next_siblings("c")}',end="\n\n") 输出结果 --- b_tag.find_next_sibling(): <c><c class="hello"><div>text2</div></c></c> b_tag.find_next_siblings(): [<c><c class="hello"><div>text2</div></c></c>, <c class="world" id="link3"><div>text3</div></c>]
-
find_previous_siblings()
和find_previous_sibling()
: 通过.previous_siblings
属性对当前tag
的前面兄弟tag
节点进行迭代, 搜索当前节点前面的兄弟节点,搜索方法的参数使用同find_all
。html_doc = """ <a><b>text1</b><c><c class="hello"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) div_tag = soup.c.div '''find_previous_siblings() 和 find_previous_sibling(): 通过 .previous_siblings 属性对当前tag的前面解析的兄弟tag节点进行迭代''' c_tag = soup.c print(f'c_tag.find_previous_sibling(): {c_tag.find_previous_sibling("b")}') print(f'c_tag.find_previous_siblings(): {c_tag.find_previous_siblings("b")}',end="\n\n") 输出结果 --- c_tag.find_previous_sibling(): <b>text1</b> c_tag.find_previous_siblings(): [<b>text1</b>]
-
find_all_next()
和find_next()
:通过.next_elements
属性对当前tag
的之后的tag
和字符串进行迭代。html_doc = """ <a><b>text1</b><c><c class="hello"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) div_tag = soup.c.div '''find_all_next() 和 find_next(): 通过.next_elements属性对当前tag的之后的 tag 和字符串进行迭代''' print(f'c_tag.find_next(): {c_tag.find_next("text3")}') print(f'c_tag.find_all_next(): {c_tag.find_all_next("text3")}') print(f'c_tag.find_all_next(): {c_tag.find_all_next("e")}',end="\n\n") 输出结果 --- c_tag.find_next(): None c_tag.find_all_next(): [] c_tag.find_all_next(): [<e>text4</e>]
-
find_all_previous()
和find_previous()
: 通过.previous_elements
属性对当前tag的之前的 tag 和字符串进行迭代。html_doc = """ <a><b>text1</b><c><c class="hello"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e>text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # soup = BeautifulSoup(BeautifulSoup(html_doc).prettify()) div_tag = soup.c.div '''find_all_previous() 和 find_previous(): 通过.previous_elements属性对当前tag的之前的 tag 和字符串进行迭代''' print(f'c_tag.find_previous(): {c_tag.find_previous("text1")}') print(f'c_tag.find_all_previous(): {c_tag.find_all_previous("text1")}') print(f'c_tag.find_all_previous(): {c_tag.find_all_previous("b")}',end="\n\n") 输出结果 --- c_tag.find_previous(): None c_tag.find_all_previous(): [] c_tag.find_all_previous(): [<b>text1</b>]
c)css选择器
对于熟悉CSS选择器语法的人,直接使用css选择器相较于直接使用find_xxx()
会比较方便
-
通过tag标签逐层查找:
html_doc = """ <a><b>text1</b><c><c class="hello" id="link2"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e class="hello">text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # 通过tag标签逐层查找 print(f'soup.select("html a b") : {soup.select("html a b")}',end="\n\n") #不能写成soup.select("html.a.b") 输出结果 --- soup.select("html a b") : [<b>text1</b>]
-
找到某个tag标签下的直接子标签:
html_doc = """ <a><b>text1</b><c><c class="hello" id="link2"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e class="hello">text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # 找到某个tag标签下的直接子标签 print(f'soup.select("a > c > c") : {soup.select("a > c > c")}',end="\n\n") 输出结果 --- soup.select("a > c > c") : [<c class="hello" id="link2"><div>text2</div></c>]
-
按id查询某个a标签下的直接子标签:
html_doc = """ <a><b>text1</b><c><c class="hello" id="link2"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e class="hello">text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # 按id查询某个a标签下的直接子标签 print(f'soup.select("a > #link3") : {soup.select("a > #link3")}',end="\n\n") 输出结果 --- soup.select("a > #link3") : [<c class="world" id="link3"><div>text3</div></c>]
-
按class查询:
html_doc = """ <a><b>text1</b><c><c class="hello" id="link2"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e class="hello">text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) # 按class查询某个c标签 print(f'soup.select("c .hello") : {soup.select("c .hello")}') # 按class查询某个a标签下的直接子标签 print(f'soup.select("a > .hello") : {soup.select("a > .hello")}',end="\n\n") 输出结果 --- soup.select("c .hello") : [<c class="hello" id="link2"><div>text2</div></c>] soup.select("a > .hello") : [<e class="hello">text4</e>]
-
同时用多种CSS选择器查询元素:
html_doc = """ <a><b>text1</b><c><c class="hello" id="link2"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e class="hello">text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) #同时用多种CSS选择器查询元素 # 分别查找class =.hello和.world的标签,查找为[] print(f'soup.select(".hello,.world") : {soup.select("#hello,#world")}') # 分别查找id=link2和link3的标签,,查找多个标签(因为id具有唯一性,而class没有) print(f'soup.select("#link2,#link3") : {soup.select("#link2,#link3")}',end="\n\n") 输出结果 --- soup.select(".hello,.world") : [] soup.select("#link2,#link3") : [<c class="hello" id="link2"><div>text2</div></c>, <c class="world" id="link3"><div>text3</div></c>]
-
通过是否存在某个属性来查找:
html_doc = """ <a><b>text1</b><c><c class="hello" id="link2"><div>text2</div></c></c><c class="world" id="link3"><div>text3</div></c><e class="hello">text4</e></a> <div data-foo="value">foo!</div> """ soup = BeautifulSoup(html_doc) #通过是否存在某个属性来查找 print(f'soup.select("c[class=hello]") : {soup.select("c[class=hello]")}') 输出结果 --- soup.select("c[class=hello]") : [<c class="hello" id="link2"><div>text2</div></c>]
4)文档修改
a)直接赋值修改
-
修改
tag
的名称和属性'''1、修改tag的名称和属性''' soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') tag = soup.b origin_tag = copy.copy(tag) tag.name = "blockquote" tag['class'] = 'verybold' tag['id'] = 1 print(f"origin_tag : {origin_tag}, tag : {tag}",end="\n\n") 输出结果 --- origin_tag : <b class="boldest">Extremely bold</b>, tag : <blockquote class="verybold" id="1">Extremely bold</blockquote>
-
通过
.string
修改tag中的内容'''2、通过.string修改tag中的内容''' soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') tag = soup.b origin_tag = copy.copy(tag) tag.string = "Extremely light" print(f"origin_tag : {origin_tag}, tag : {tag}",end="\n\n") 输出结果 --- origin_tag : <b class="boldest">Extremely bold</b>, tag : <b class="boldest">Extremely light</b>
b)append追加内容(标签/文本)
-
在
tag
中追加字符串内容'''3、向tag中添加内容 append()''' soup = BeautifulSoup("<a>Foo</a>") origin_soup = copy.copy(soup) soup.a.append("Bar") print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"soup.a.contents : {soup.a.contents}") #a标签有两个直接的子节点(两个NavigableString对象):['Foo', 'Bar'] #soup.a.append("Bar")等价于 new_str = NavigableString("Bar"), soup.a.append(new_str) new_str = NavigableString("Tar") soup.a.append(new_str) print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"soup.a.contents : {soup.a.contents}") # a标签有两个直接的子节点(两个NavigableString对象):['Foo', 'Bar'] # soup.a.append("Bar")等价于 soup.a.append(soup.new_string("Nar",NavigableString)),注意soup对象才有new_string方法,tag对象没有 # soup.a.new_string("Nar",NavigableString) #TypeError: 'NoneType' object is not callable new_str = soup.new_string("Nar",NavigableString) soup.a.append(new_str) print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"soup.a.contents : {soup.a.contents}", end="\n\n") # a标签有两个直接的子节点(两个NavigableString对象):['Foo', 'Bar'] 输出结果 --- origin_soup : <html><body><a>Foo</a></body></html>, soup : <html><body><a>FooBar</a></body></html> soup.a.contents : ['Foo', 'Bar'] origin_soup : <html><body><a>Foo</a></body></html>, soup : <html><body><a>FooBarTar</a></body></html> soup.a.contents : ['Foo', 'Bar', 'Tar'] origin_soup : <html><body><a>Foo</a></body></html>, soup : <html><body><a>FooBarTarNar</a></body></html> soup.a.contents : ['Foo', 'Bar', 'Tar', 'Nar']
-
在
tag
中追加注释'''4、创建一段注释(字符串带<!--xxx-->),再使用append追加到该节点内的尾部:soup.new_string("Nice to see you.", Comment)''' soup = BeautifulSoup("<a>Foo</a>") # comment = soup.a.new_string("Nar",Comment) #TypeError: 'NoneType' object is not callable comment = soup.new_string("This is a comment",Comment) soup.a.append(comment) print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"soup.a.contents : {soup.a.contents}", end="\n\n") # a标签有两个直接的子节点(两个NavigableString对象):['Foo', 'Bar'] 输出结果 --- origin_soup : <html><body><a>Foo</a></body></html>, soup : <html><body><a>Foo<!--This is a comment--></a></body></html> soup.a.contents : ['Foo', 'This is a comment']
-
在
tag
中追加一个tag
标签'''5、BeautifulSoup.new_tag() 创建一个tag,再使用append追加到该节点内的尾部''' soup = BeautifulSoup("<b></b>") original_tag = copy.copy(soup.b) tag = soup.b new_tag = soup.new_tag("a", href="http://www.example.com") tag.append(new_tag) print(f"original_tag : {original_tag}, tag : {tag}") print(f"tag : {tag.contents}", end="\n\n") # a标签有两个直接的子节点(两个NavigableString对象):['Foo', 'Bar'] 输出结果 --- original_tag : <b></b>, tag : <b><a href="http://www.example.com"></a></b> tag : [<a href="http://www.example.com"></a>]
c)insert指定位置插入(标签/文本)
-
按位置插入到
tag
标签'''6、tag.insert() 按位置插入到标签内''' # Tag.insert() 方法与 Tag.append() 方法类似,区别是不会把新元素添加到父节点 .contents 属性的最后, # 而是把元素插入到指定的位置.与Python列表总的 .insert() 方法的用法相同 soup = BeautifulSoup("<b>Hello world <i>Ni hao</i></b>") original_tag = copy.copy(soup.b) tag = soup.b tag.insert(1, "Nar_test") print(f"original_tag : {original_tag}, tag : {tag}") print(f"original_tag.contents : {original_tag.contents}, tag.contents : {tag.contents}", end="\n\n") 输出结果 --- original_tag : <b>Hello world <i>Ni hao</i></b>, tag : <b>Hello world Nar_test<i>Ni hao</i></b> original_tag.contents : ['Hello world ', <i>Ni hao</i>], tag.contents : ['Hello world ', 'Nar_test', <i>Ni hao</i>]
-
insert_before()
方法在当前tag
或文本节点前插入内容'''7、insert_before() 方法在当前tag或文本节点前插入内容''' soup = BeautifulSoup("<a><b>Hello world <i>Ni hao</i></b></a>") original_soup = copy.copy(soup) soup.a.b.insert_before("Nar_test") #在b标签前insert_before print(f"original_soup : {original_soup}, soup : {soup}") print(f"original_soup.contents : {original_soup.contents}, soup.contents : {soup.contents}", end="\n\n") soup = BeautifulSoup("<a><b>Hello world <i>Ni hao</i></b></a>") original_soup = copy.copy(soup) # content_str = soup.b.string #None,因为b标签内还有标签,String必须是最后一层 content_str = soup.a.b.contents[0] #在b标签内文本前insert_before content_str.insert_before("Nar_test") #如果直接使用soup.b.string会报错:'NoneType' object has no attribute 'insert_before' print(f"original_soup : {original_soup}, soup : {soup}") print(f"original_soup.contents : {original_soup.contents}, soup.contents : {soup.contents}", end="\n\n") 输出结果 --- original_soup : <html><body><a><b>Hello world <i>Ni hao</i></b></a></body></html>, soup : <html><body><a>Nar_test<b>Hello world <i>Ni hao</i></b></a></body></html> original_soup.contents : [<html><body><a><b>Hello world <i>Ni hao</i></b></a></body></html>], soup.contents : [<html><body><a>Nar_test<b>Hello world <i>Ni hao</i></b></a></body></html>] original_soup : <html><body><a><b>Hello world <i>Ni hao</i></b></a></body></html>, soup : <html><body><a><b>Nar_testHello world <i>Ni hao</i></b></a></body></html> original_soup.contents : [<html><body><a><b>Hello world <i>Ni hao</i></b></a></body></html>], soup.contents : [<html><body><a><b>Nar_testHello world <i>Ni hao</i></b></a></body></html>]
-
insert_after()
方法在当前tag
或文本节点后插入内容(同insert_before()
)
d)clear & extract & decompose:移除内容/移除标签
-
Tag.clear()
方法移除当前tag
内的字符串内容'''8、Tag.clear() 方法移除当前tag内的字符串内容''' soup = BeautifulSoup("<b>Hello world <i>Ni hao</i></b>") origin_soup = copy.copy(soup) tag = soup.i tag.clear() #移除当前tag的内容 print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"origin_soup.contents : {origin_soup.contents}, soup.contents : {soup.contents}", end="\n\n") 输出结果 --- origin_soup : <html><body><b>Hello world <i>Ni hao</i></b></body></html>, soup : <html><body><b>Hello world <i></i></b></body></html> origin_soup.contents : [<html><body><b>Hello world <i>Ni hao</i></b></body></html>], soup.contents : [<html><body><b>Hello world <i></i></b></body></html>]
-
Tag.extract()
方法将当前tag
移除文档树,并作为方法结果返回'''9、PageElement.extract()方法将当前tag移除文档树,并作为方法结果返回''' soup = BeautifulSoup("<b>Hello world <i>Ni hao</i></b>") origin_soup = copy.copy(soup) tag = soup.i res = tag.extract() # 移除当前tag节点,返回值为该tag节点 print(f"res : {res}") print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"origin_soup.contents : {origin_soup.contents}, soup.contents : {soup.contents}", end="\n\n") 输出结果 --- res : <i>Ni hao</i> origin_soup : <html><body><b>Hello world <i>Ni hao</i></b></body></html>, soup : <html><body><b>Hello world </b></body></html> origin_soup.contents : [<html><body><b>Hello world <i>Ni hao</i></b></body></html>], soup.contents : [<html><body><b>Hello world </b></body></html>]
-
Tag.decompose()
方法将当前节点移除文档树并完全销毁,返回结果为None
'''10、Tag.decompose() 方法将当前节点移除文档树并完全销毁''' soup = BeautifulSoup("<b>Hello world <i>Ni hao</i></b>") origin_soup = copy.copy(soup) tag = soup.i res = tag.decompose() # 移除当前tag节点,返回值为None print(f"res : {res}") print(f"origin_soup : {origin_soup}, soup : {soup}") print(f"origin_soup.contents : {origin_soup.contents}, soup.contents : {soup.contents}", end="\n\n") 输出结果 --- res : None origin_soup : <html><body><b>Hello world <i>Ni hao</i></b></body></html>, soup : <html><body><b>Hello world </b></body></html> origin_soup.contents : [<html><body><b>Hello world <i>Ni hao</i></b></body></html>], soup.contents : [<html><body><b>Hello world </b></body></html>]
e)wrap & unwrap:包装和解包
-
Tag.replace_with()
方法移除文档树中的某段内容,并用新tag
或文本节点替代它(标签替换成另一个标签或文本)'''11、PageElement.replace_with()方法移除文档树中的某段内容,并用新tag或文本节点替代它(标签替换成另一个标签或文本)''' soup = BeautifulSoup("<b>Hello world <i>Ni hao</i></b>") origin_soup = copy.copy(soup) b_tag = soup.b new_tag = soup.new_tag("c") new_tag.string = "example.net" b_tag.i.replace_with(new_tag) print(f"origin_soup : {origin_soup}, soup : {soup}", end="\n\n") 输出结果 --- origin_soup : <html><body><b>Hello world <i>Ni hao</i></b></body></html>, soup : <html><body><b>Hello world <c>example.net</c></b></body></html>
-
Tag.wrap()
方法可以对指定的tag
元素进行包装,并返回包装后的结果'''12、PageElement.wrap()方法可以对指定的tag元素进行包装,并返回包装后的结果''' soup = BeautifulSoup("<p>I wish I was bold.</p>") origin_soup = copy.copy(soup) soup.p.string.wrap(soup.new_tag("b")) #在p标签内的String外, 包装一个<b></b> print(f"origin_soup : {origin_soup}, soup : {soup}") # <b>I wish I was bold.</b> soup.p.wrap(soup.new_tag("div")) #在p标签外,包装一个<div></div> print(f"origin_soup : {origin_soup}, soup : {soup}", end="\n\n") 输出结果 --- origin_soup : <html><body><p>I wish I was bold.</p></body></html>, soup : <html><body><p><b>I wish I was bold.</b></p></body></html> origin_soup : <html><body><p>I wish I was bold.</p></body></html>, soup : <html><body><div><p><b>I wish I was bold.</b></p></div></body></html>
-
Tag.unwrap()
方法与wrap()
方法相反,该方法常被用来进行标记的解包'''12、Tag.unwrap()方法与 wrap() 方法相反,该方法常被用来进行标记的解包''' soup = BeautifulSoup("<div><p><a>I wish I was bold.</a></p></div>") origin_soup = copy.copy(soup) soup.p.unwrap() # 移除掉p标签,但是不移除标签内的其他标签或文本 print(f"origin_soup : {origin_soup}, soup : {soup}") 输出结果 --- origin_soup : <html><body><div><p><a>I wish I was bold.</a></p></div></body></html>, soup : <html><body><div><a>I wish I was bold.</a></div></body></html>
3、Selenium基础介绍
参考
-
Selenium入门指南
-
Selenium Python Tutorial - GeeksforGeeks
-
UI自动化告别手动下载驱动(使用WebDriverManager 自动下载管理driver)
-
Python3 网络爬虫(六):Python 使用 selenium 模拟登陆淘宝
-
Jack Cui博客
1)安装浏览器驱动
参考安装浏览器驱动 | Selenium,UI自动化告别手动下载驱动(使用WebDriverManager 自动下载管理driver)
大多数机器会自动更新浏览器, 但不会自动更新驱动程序。 为了确保为浏览器提供正确的驱动程序,一种较为灵活的解决方法是通过安装webdriver-manager
来实现(也可以手动配置chromedriver.exe
驱动的环境变量)。
-
先安装
webdriver-manager
第三方库:pip install webdriver-manager -i https://pypi.tuna.tsinghua.edu.cn/simple/
-
再通过
webdriver-manager
实现浏览器驱动(Chrome/Edge driver)的自动更新# selenium 4的写法 from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.microsoft import EdgeChromiumDriverManager #自动更新chrome驱动 driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) #自动更新Edge驱动 driver = webdriver.Edge(service=ChromeService(EdgeChromiumDriverManager().install()))
2)第一个Selenium脚本
参考编写第一个Selenium脚本 | Selenium
-
假设要自动化测试的网页是:Web form
-
要进行的操作是:输入内容到文本框,点击提交,并输出响应结果
-
Python
代码需要完成如下7个步骤的操作:-
使用驱动实例开启会话
-
在浏览器上执行操作, 导航到一个网页
-
请求浏览器信息(可有可无)
-
建立等待策略
-
查找元素,并完成表单填写和提交操作
-
在响应页面中获取响应结果
-
结束会话
-
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from selenium.webdriver.common.by import By
def test_eight_components(driver):
#Step1: 使用驱动实例开启会话
# driver = webdriver.Chrome()
# driver = webdriver.Edge()
driver = driver
#Step2: 在浏览器上执行操作, 导航到一个网页
driver.get("https://www.selenium.dev/selenium/web/web-form.html")
#Step3: 请求浏览器信息,包括窗口句柄、浏览器尺寸/位置、cookie、警报等
title = driver.title
print(f'title == "Web form" : {title == "Web form"}')
#Step4: 建立等待策略: 因为我们希望在尝试定位元素之前, 确保该元素位于页面上, 并且在尝试与该元素交互之前, 该元素处于可交互状态.
# 隐式等待很少是最好的解决方案, 但在这里最容易演示
driver.implicitly_wait(0.5) #更多的等待策略参考 https://www.selenium.dev/zh-cn/documentation/webdriver/waits/
#Step5: 发送命令,查找元素: 大多数Selenium会话中的主要命令都与元素相关, 如果不先找到元素, 就无法与之交互
text_box = driver.find_element(by=By.NAME, value="my-text") #Text input
submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button") #点击按钮
#Step6: 操作元素:通常对于一个元素, 只有少数几个操作可以执行
text_box.send_keys("Selenium") #模拟文本框输入
submit_button.click() #模拟按钮点击
#Step7: 在响应页面中查找元素并获取元素信息
message = driver.find_element(by=By.ID, value="message")
value = message.text
print(f'title == "Received" : {title == "Web form"}')
#Step8: 结束会话
driver.quit()
if __name__ == '__main__':
# 自动更新驱动 参考https://www.cnblogs.com/bao-yan/p/16480026.html
# driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) #自动更新chrome驱动
driver = webdriver.Edge(service=ChromeService(EdgeChromiumDriverManager().install())) # 自动更新Edge驱动
test_eight_components(driver=driver)
Note:
-
如果不想自动打开浏览器,可以使用如下参数
option.headless = True
设置webdriver
对象(参考Python使用Selenium Webdriver爬取网页所有内容))from selenium.webdriver import ChromeOptions option = ChromeOptions() # 设置浏览器为无头模式,使用过程中不会弹出浏览器页面 option.headless = True driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()),options=option) #自动更新chrome驱动
3)常用方法
参考Selenium Python Tutorial - GeeksforGeeks
a)find_element()
-
find_element_by_id()
是selenium3
的写法,selenium4
写法如下:driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''selenium3: find_element_by_id()''' try: id_el = driver.find_element(by=By.ID,value="newsFieldWrap") driver.implicitly_wait(3) # 隐式等待3s print(f"按元素的ID属性查询元素 - driver.find_element(by=By.ID,value) : {id_el}") print(f"查询元素内容 : {id_el.text}",end="\n\n") except Exception as e: print(e) 输出结果 --- 按元素的ID属性查询元素 - driver.find_element(by=By.ID,value) : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="7ca2b278-bcaa-4dd6-8b89-7bd9fea46f33")> 查询元素内容 : LINE FRIENDS旗下创意互动品牌COLLER全新上市,探索品牌出圈新范式 1 分钟前 ST天马:2月28日起撤销公司股票交易其他风险警示 2 分钟前 三五互联:控股孙公司拟增资扩股引入投资者 2 分钟前 科德数控业绩快报:2022年度归母净利润6039.46万元 下降17.12% 2 分钟前 友发集团:部分经销商增持1.48%股份 增持完毕 7 分钟前 首个大湾区大型旅行团抵达香港迪士尼 7 分钟前 富创精密:2022年净利润2.4亿元,同比增长89.71% 17 分钟前 日媒:芯片行业加倍看好新加坡作为生产中心
-
find_element_by_name()
是selenium3
的写法,selenium4
写法如下:driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_name()''' try: name_el = driver.find_element(by=By.NAME,value="description") driver.implicitly_wait(3) # 隐式等待3s print(f'按元素的name属性查询元素 - driver.find_element(by=By.NAME,value="description") : {name_el}') print(f"查询元素内容 : {name_el.text}",end="\n\n") except Exception as e: print(e) 输出结果 --- 按元素的name属性查询元素 - driver.find_element(by=By.NAME,value="description") : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="593edc0c-c3d8-43f3-8c70-76ab71339e01")> 查询元素内容 :
-
find_element_by_xpath()
是selenium3
的写法,selenium4
写法如下:driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_xpath()''' try: xpath = '//*[@id="eo-app"]/div[2]/div/div[1]/div[1]/div/div[1]/div[2]/div[1]/a/p' #xpath是基于XML的树状结构,可作为小型的查询语言,在网页中Copy即可 xpath_el = driver.find_element(by=By.XPATH, value=xpath) driver.implicitly_wait(3) # 隐式等待3s print(f"按元素的xpath查询元素 - driver.find_element(by=By.XPATH, value=xpath) : {xpath_el}") print(f"查询元素内容 : {xpath_el.text}",end="\n\n") except Exception as e: print(e) 输出结果 --- 按元素的xpath查询元素 - driver.find_element(by=By.XPATH, value=xpath) : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="5e158422-da4a-4292-a76c-1f4461b84385")> 查询元素内容 : 比亚迪逆天改命,王传福赌赢了
-
find_element_by_link_text()
是selenium3
的写法,selenium4
写法如下(注意标签必须是<a></a>
的元素,才能成功,参考https://www.jianshu.com/p/9006ba8478e6):driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_link_text()''' #参考 https://www.jianshu.com/p/9006ba8478e6 try: # linkText_el = driver.find_element(by=By.LINK_TEXT, value="行业洞察") linkText_el = driver.find_element(by=By.LINK_TEXT, value="消费生活") #用 find_element_by_link_text 方式定位,标签必须是<a></a>的元素,才能成功。 driver.implicitly_wait(3) # 隐式等待3s print(f"按a标签的linkText查询 - driver.find_element(by=By.linkText_el, value=linkText_el) : {linkText_el}") print(f"查询元素内容 : {linkText_el.text}",end="\n\n") except Exception as e: print(e) 输出结果 --- 按a标签的linkText查询 - driver.find_element(by=By.linkText_el, value=linkText_el) : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="36ab6667-de33-44d5-8418-e7b846024970")> 查询元素内容 : 消费生活
-
find_element_by_partial_link_text()
是selenium3
的写法,selenium4
写法如下:driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_partial_link_text()''' try: partial_linkText_el = driver.find_element(by=By.PARTIAL_LINK_TEXT, value="行业") #标签必须是<a></a>的元素,才能成功 driver.implicitly_wait(3) # 隐式等待3s print(f"按a标签的PARTIAL_LINK_TEXT查询 - driver.find_element(by=By.PARTIAL_LINK_TEXT, value=partial_linkText_el) : {partial_linkText_el}") print(f"查询元素内容 : {partial_linkText_el.text}", end="\n\n") except Exception as e: print(e) 输出结果 --- 按a标签的PARTIAL_LINK_TEXT查询 - driver.find_element(by=By.PARTIAL_LINK_TEXT, value=partial_linkText_el) : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="408eeab4-e8ac-4205-8d8d-7b06bfe41c68")> 查询元素内容 : 行业洞察
-
find_element_by_tag_name()
是selenium3
的写法,selenium4
写法如下:driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_tag_name()''' try: tag_el = driver.find_element(by=By.TAG_NAME, value="a") #标签必须是<a></a>的元素,才能成功 driver.implicitly_wait(3) # 隐式等待3s print(f"按标签名TAG_NAME查询 - driver.find_element(by=By.TAG_NAME, value='a') : {tag_el}") print(f"查询元素内容 : {tag_el}", end="\n\n") except Exception as e: print(e) 输出结果 --- 按标签名TAG_NAME查询 - driver.find_element(by=By.TAG_NAME, value='a') : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="9d9ecea3-5da3-453d-a145-17f26ed93282")> 查询元素内容 : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="9d9ecea3-5da3-453d-a145-17f26ed93282")>
-
find_element_by_class_name()
是selenium3
的写法,selenium4
写法如下(注意对于多个class_name
,只能输入一个class_name
, 参考https://blog.csdn.net/A_under_taker/article/details/109482165):driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_class_name()''' #参考 https://blog.csdn.net/A_under_taker/article/details/109482165 try: # class_el = driver.find_element(by=By.CLASS_NAME, value="common-new-info eo-bottom-dashed") #Message: no such element: Unable to locate element: {"method":"css selector","selector":".common-new-info eo-bottom-dashed"} (Session info: chrome=101.0.4951.54) class_el = driver.find_element(by=By.CLASS_NAME, value="common-new-info") #对于多个class_name(比如common-new-info eo-bottom-dashed),只能输入一个class_name, driver.implicitly_wait(3) # 隐式等待3s print(f"按元素CLASS_NAME属性查询元素 - driver.find_element(by=By.CLASS_NAME, value="") : {class_el}") print(f"查询元素内容 : {class_el.text}", end="\n\n") except Exception as e: print(e) 输出结果 --- 按元素CLASS_NAME属性查询元素 - driver.find_element(by=By.CLASS_NAME, value=) : {class_el} 查询元素内容 : 汽车出行 比亚迪逆天改命,王传福赌赢了 2023-02-24 16:26
-
find_element_by_css_selector()
是selenium3
的写法,selenium4
写法如下(多个属性组合定位参考https://blog.csdn.net/qq_35861801/article/details/108125082):driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) # 自动更新chrome驱动, 获取浏览器对话实例 driver.get("https://www.iyiou.com/") #输入网址,发起请求 driver.implicitly_wait(3) #隐式等待3s '''find_element_by_css_selector()''' #参考 https://blog.csdn.net/qq_35861801/article/details/108125082 try: #多个属性组合定位 css_selector_el = driver.find_element(by=By.CSS_SELECTOR, value='div[class="eo-briefing-time"]') #标签必须是<a></a>的元素,才能成功 driver.implicitly_wait(3) # 隐式等待3s print(f'按标签名CSS_SELECTOR查询 - driver.find_element(by=By.CSS_SELECTOR, value="div[class="eo-briefing-time"]") : {css_selector_el}') print(f"查询元素内容 : {css_selector_el}", end="\n\n") except Exception as e: print(e) 输出结果 --- 按标签名CSS_SELECTOR查询 - driver.find_element(by=By.CSS_SELECTOR, value="div[class="eo-briefing-time"]") : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="c6ab1f93-aa24-4c8f-b746-37b7be438644")> 查询元素内容 : <selenium.webdriver.remote.webelement.WebElement (session="7199bfa3f8ef10ab5749f3982ab29123", element="c6ab1f93-aa24-4c8f-b746-37b7be438644")>
b)find_elements()
可以一次性查询多个元素并以列表的形式返回,使用方法同find_element()
。
c)获取/设置 cookies 或 token
参考python + selenium token登录,python+selenium 通过添加cookies或token解决网页上验证码登录问题
-
获取并设置
cookie
:from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.implicitly_wait(10) driver.get("https://www.dydata.io/") # 镝数聚 #获取cookies cookies = driver.get_cookies() #type=list print(f"cookies = {cookies}") #添加cookies for cookie in cookies: driver.add_cookie(cookie) #提交所有cookie # 将session_id装到cookie中 driver.add_cookie({ "domain": "www.dydata.io", "name": "session_id", "value": driver.session_id, "path": '/', "expires": None })
-
获取并设置
token
:from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome() driver.implicitly_wait(10) driver.get("https://www.dydata.io/") # 镝数聚 #获取token token = driver.execute_script(f'window.localStorage.getItem("token"') print(f"token = {token}") #设置token token = 'eyJxxxDk54Q' js = f'window.localStorage.setItem("token", "{token}")' driver.execute_script(js) driver.get('https://www.iyiou.com/') # 网址是登录后的主页网址
d)下拉滚动条
参考
-
【selenium】下拉滚动条_selenium下拉框滚动条
-
python+selenium 滚动条/内嵌滚动条循环下滑,判断是否滑到最底部_selenium 判断滑动鼠标到底
4)模拟登录淘宝
参考Python3 网络爬虫(六):Python 使用 selenium 模拟登陆淘宝,Selenium被检测为爬虫,怎么屏蔽和绕过
a)selenium自动开启新的浏览器(登录失败)
import time
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
# 使用 Selenium 的 ActionChains 方法,拖动滑块至最右边
def slide_action(driver, action_chains, xpath):
# 点击登录后会出现滑块
try:
# 出现验证码,滑动验证
slider = driver.find_element(By.XPATH, xpath)
if slider.is_displayed():
# 拖拽滑块
action_chains.drag_and_drop_by_offset(slider, 258, 0).perform()
time.sleep(0.5)
# 释放滑块,相当于点击拖拽之后的释放鼠标
action_chains.release().perform()
except (Exception):
print('未出现登录验证码')
#参考 https://blog.csdn.net/c406495762/article/details/106757531
if __name__ == '__main__':
driver = webdriver.Chrome()
driver.get("https://www.taobao.com") #访问淘宝
driver.implicitly_wait(1) #隐式等待1s
'''*************************** 模拟登录淘宝 *******************************'''
'''Step1: 模拟登录点击'''
# 通过xpath查找元素, 并点击登录
login_xpath = r'//*[@id="J_SiteNavLogin"]/div[1]/div[1]/a[1]'
# driver.find_element_by_xpath(xpath).click() # 如果报了AttributeError: ‘WebDriver‘ object has no attribute ‘find_element_by_xpath,
# 参考https://blog.csdn.net/m0_52818006/article/details/126283288
driver.find_element(By.XPATH,login_xpath).click() #模拟登录点击
'''Step2: 输入用户名和密码'''
username_xpath = '//*[@id="fm-login-id"]'
password_xpath = '//*[@id="fm-login-password"]'
login_click_xpath = '//*[@id="login-form"]/div[4]/button'
slide_xpath = "//span[contains(@class, 'btn_slide')]"
driver.find_element(By.XPATH,username_xpath).send_keys("15xxx2230")
driver.find_element(By.XPATH,password_xpath).send_keys("1046xxx88aA")
# driver.find_element(By.XPATH,login_click_xpath).click()
# 使用 Selenium 的 ActionChains 方法,拖动滑块至最右边
action_chains = ActionChains(driver)
#selenium会被taobao的指纹识别算法检测到, window.navigator.webdriver=True, 但即使修改了该特征也不能避开指纹识别
try:
slide_action(driver, action_chains, slide_xpath) #可能会在点击前出现滑块
driver.find_element(By.XPATH,login_click_xpath).click() #点击登录
slide_action(driver, action_chains,slide_xpath) #可能会在点击后出现滑块
driver.find_element(By.XPATH, login_click_xpath).click() # 点击登录
except(Exception):
print("登录处理出错")
pass
time.sleep(10)
在输入完用户名和密码,在点击登录之前,会有一个滑动条,有博主说滑动条可以使用Selenium
的 ActionChains
方法来实现,但是我在使用时,虽然能通过xpath
能获取到元素,但是在调用action_chains.drag_and_drop_by_offset(slider, 258, 0).perform()
时会报错:{ElementNotInteractableException}Message: element not interactable: [object HTMLSpanElement] has no size and location
;
而且即使手动滑动滚动条也不能登录,主要原因是taobao
有反Selenium
爬虫的指纹识别算法,可以检测出当前的调用者是人工的还是selenium,如果是自动调用的,在控制台中window.navigator.webdriver=True
,即使修改了该特征也不能避开指纹识别。
上面的代码识别效果如下:
b)selenium绑定用户开启的浏览器(登录成功)
参考Selenium被检测为爬虫,怎么屏蔽和绕过,Selenium连接已存在的浏览器1 | 技术九柄剑
上面的方法不成功的主要原因是Taobao
的反爬虫算法,Taobao
可以通过检测浏览器window.navigator.webdriver=True
等特征来判断此时是否存在爬虫, 通常我们可以使用 sannysoft来检测浏览器是否是由自动化爬虫工具(selenium、puppeter、playwright
等)自动开启。
目前较好的解决方法是让selenium
连接手动开启的浏览器,不让其自动开启:
-
首先通过对
chrome.exe
快捷方式中配置chrome
的启动参数,命令如下:chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenium\ChromeProfile"
配置方式如下:
-
配置好了远程调试地址
127.0.0.1:9222
之后,接着使用如下代码让selenium
连接手动打开的浏览器。import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver import ActionChains from code003_taobao_login_test import slide_action if __name__ == '__main__': sec = 1 #selenium不通过浏览器驱动自动打开浏览器,而是连接手动打开的浏览器 chrome_options = Options() chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") #通过设置参数,让selenium登录手动打开的浏览器 driver = webdriver.Chrome(options=chrome_options) print(driver.title) driver.get("https://www.taobao.com") #访问淘宝 driver.implicitly_wait(3) #隐式等待3s '''*************************** 模拟登录淘宝 *******************************''' '''Step1: 模拟登录点击''' # 通过xpath查找元素, 并点击登录 login_xpath = r'//*[@id="J_SiteNavLogin"]/div[1]/div[1]/a[1]' # driver.find_element_by_xpath(xpath).click() # 如果报了AttributeError: ‘WebDriver‘ object has no attribute ‘find_element_by_xpath, # 参考https://blog.csdn.net/m0_52818006/article/details/126283288 driver.find_element(By.XPATH,login_xpath).click() time.sleep(sec) #模拟登录点击,注意需要等待一定时间后才可以跳转到登录页面 # driver.implicitly_wait(1) #隐式等待1s '''Step2: 输入用户名和密码''' username_xpath = '//*[@id="fm-login-id"]' password_xpath = '//*[@id="fm-login-password"]' login_click_xpath = '//*[@id="login-form"]/div[4]/button' slide_xpath = '//*[@id="nc_1__scale_text"]/span' driver.find_element(By.XPATH,username_xpath).send_keys("1560***230") time.sleep(sec) driver.find_element(By.XPATH,password_xpath).send_keys("104***8aA") time.sleep(sec) # 使用 Selenium 的 ActionChains 方法,拖动滑块至最右边 action_chains = ActionChains(driver) #selenium会被taobao的指纹识别算法检测到, window.navigator.webdriver=True, 但即使修改了该特征也不能避开指纹识别 try: slide_action(driver, action_chains, slide_xpath) time.sleep(sec) #可能会在点击前出现滑块 driver.find_element(By.XPATH, login_click_xpath).click() time.sleep(sec) #点击登录 slide_action(driver, action_chains,slide_xpath) time.sleep(sec) #可能会在点击后出现滑块 driver.find_element(By.XPATH, login_click_xpath).click() time.sleep(sec) # 点击登录 except(Exception): print("登录处理出错") pass
其中使用的
slide_action()
见4).a)。上面代码的实现效果如下(前提是需要手动开启浏览器,然后让
selenium
去连接):
会发现手动开启的浏览器在登录账户时并不用滑动滚动条,并且登录成功后,如果不退出账户,下次即使手动关闭了浏览器,selenium
再次访问taobao
时会自动登录账号,主要原因是手动打开的浏览器中有session_id
缓存数据,默认7天后账号登录失效。
5)其他问题
a)关闭多余标签页
-
对于多标签页来说,如果在使用
driver.find_element(By.XPATH,xpath).click()
时,xpath
识别不了,抛出selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element
的错误,其中大概率是当前driver
调用的网页窗口并不存在该xpath
或者是当前页面没有完全加载出来,可以通过如下方法来修改,参考selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element#driver切换至指定标签页 current_handle = driver.current_window_handle handles = driver.window_handles #获取所有处理页,type=list[str] for handle in handles: driver.switch_to.window(handle) driver.close #关闭当前标签页 time.sleep(1) #处理逻辑 ... #driver设置隐式等待 time.sleep(1) 或者 driver.implicitly_wait(1)
-
关闭当前标签页:参考Selenium基础之关闭页面和浏览器_selenium关闭浏览器_JaneMiss的博客-CSDN博客
Selenium中有两种关闭浏览器的方式,这两种方式的区别在于:
-
close()
:关闭当前标签页driver.close()
-
quite()
:关闭浏览器,释放进程。driver.quite()
-
b)获取标签页html
内容
-
获取当前标签页网址和的
html
内容:-
获取当前标签页网址:
driver.current_url
-
获取当前标签页
html
内容(设置延时即可获取动态html
):driver.get("xxx.html") # up主首页 driver.implicitly_wait(10) # 搜索等待时间设置长点 html_doc = driver.page_source
-
-
selenium
与BS4
的结合:参考selenium和bs4的联合使用
c)设置浏览器不开启模式
-
selenium
设置不自动开启浏览器:参考Selenium使用ChromeOptions启动参数 - 韩志超 - 博客园from selenium.webdriver import Chrome, ChromeOptions from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager up_mainPage_url = 'https://space.bilibili.com/1641484091/?spm_id_from=333.999.0.0'# 加载动态网页 option = ChromeOptions() # 设置浏览器为无头模式,使用过程中不会弹出浏览器页面 option.headless = True driver = Chrome(service=ChromeService(ChromeDriverManager().install()), options=option) # 自动更新chrome驱动 driver.get(up_mainPage_url)
d)先悬停再点击操作
参考selenium+python鼠标悬停+点击隐藏按钮的3种方式
假设我想点击“百度股市通”页面中某只股票的“股评”按钮
如果识别该组件后直接点击是不生效的:
gupings = driver.find_elements(by=By.CSS_SELECTOR, value='div[class="santd-tabs-tab"]')
#查找哪一个elements中包含“股评”两字
for temp in gupings:
if temp.text == '股评':
guping = temp
break
guping.click() # 直接点击“股评”按钮, 此方法无效报错
解决方法是先使用ActionChains(driver).move_to_element(guping).perform()
先悬停到该组件上,接着再对该组件进行点击click()
操作:
gupings = driver.find_elements(by=By.CSS_SELECTOR, value='div[class="santd-tabs-tab"]')
#查找哪一个elements中包含“股评”两字
for temp in gupings:
if temp.text == '股评':
guping = temp
break
# guping.click() # 直接点击“股评”按钮, 此方法无效报错
ActionChains(driver).move_to_element(guping).perform() # 先悬停
guping.click() #再点击
driver.implicitly_wait(5) # 搜索等待时间设置长点
由于ActionChains
悬停的操作是异步执行的操作,如果在for
循环中执行多个ActionChains
,其实这些ActionChains
会并发执行的,这就会导致同一个对象的引用发生更改。举个例子,我想继续点击接下来页面中的“今日/本周/本月/今年”的股评按钮:
如果每次ActionChain
悬停点击后执行driver.implicitly_wait(5)
,会导致数据被篡改,比如下面这段代码大概率会出现问题:文章来源:https://www.toymoban.com/news/detail-533350.html
guping_list = driver.find_elements(by=By.CLASS_NAME, value="vote-tab-text")
guping_dict = {"今日" : "today",
"本周" : "week",
"本月" : "month",
"今年" : "year"}
'''##### 查看今日/本周/本月/今年的"股评"(先悬停再点击),并封装到stock_info_strategy ######'''
for index in range(len(guping_list)):
# print(index)
# print(guping_dict[guping_el.text])
guping_el = guping_list[index]
type = guping_dict[guping_el.text]
ActionChains(driver).move_to_element(guping_el).perform() # 先悬停
guping_el.click() # 再点击
driver.implicitly_wait(5) # 搜索等待时间设置长点
time.sleep(2) #ActionChains异步,需要通过休眠来等待前面的(今日/本周/本月/今年)ActionChains执行完,避免发生对象引用的更改,时间最好设置大些
解决方法是通过sleep(2)
,让多个ActionChains
间隔进行,避免同时修改同一个对象guping_el
,如果效果差,则sleep
时间设置长些:文章来源地址https://www.toymoban.com/news/detail-533350.html
guping_list = driver.find_elements(by=By.CLASS_NAME, value="vote-tab-text")
guping_dict = {"今日" : "today",
"本周" : "week",
"本月" : "month",
"今年" : "year"}
'''##### 查看今日/本周/本月/今年的"股评"(先悬停再点击),并封装到stock_info_strategy ######'''
for index in range(len(guping_list)):
# print(index)
# print(guping_dict[guping_el.text])
guping_el = guping_list[index]
type = guping_dict[guping_el.text]
ActionChains(driver).move_to_element(guping_el).perform() # 先悬停
guping_el.click() # 再点击
time.sleep(2) #ActionChains异步,需要通过休眠来等待前面的(今日/本周/本月/今年)ActionChains执行完,避免发生对象引用的更改,时间最好设置大些
到了这里,关于【Python爬虫】Python爬虫三大基础模块(urllib & BS4 & Selenium)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!