Scrapy
HTTP状态码
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。
HTTP 状态码的英文为 HTTP Status Code。
下面是常见的 HTTP 状态码:
- 1xx(信息性状态码):表示接收的请求正在处理。
- 2xx(成功状态码):表示请求正常处理完毕。
- 3xx(重定向状态码):需要后续操作才能完成这一请求。
- 4xx(客户端错误状态码):表示请求包含语法错误或无法完成。
- 5xx(服务器错误状态码):服务器在处理请求的过程中发生了错误。
| 分类 | 分类描述 |
|---|---|
| 1** | 信息,服务器收到请求,需要请求者继续执行操作 |
| 2** | 成功,操作被成功接收并处理 |
| 3** | 重定向,需要进一步的操作以完成请求 |
| 4** | 客户端错误,请求包含语法错误或无法完成请求 |
| 5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
HTTP状态码列表:
| 状态码 | 状态码英文名称 | 中文描述 |
|---|---|---|
| 100 | Continue | 继续。客户端应继续其请求 |
| 101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
| 200 | OK | 请求成功。一般用于GET与POST请求 |
| 201 | Created | 已创建。成功请求并创建了新的资源 |
| 202 | Accepted | 已接受。已经接受请求,但未处理完成 |
| 203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
| 204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
| 205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
| 206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
| 300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
| 301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
| 302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
| 303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
| 304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
| 305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
| 306 | Unused | 已经被废弃的HTTP状态码 |
| 307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
| 400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
| 401 | Unauthorized | 请求要求用户的身份认证 |
| 402 | Payment Required | 保留,将来使用 |
| 403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
| 404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
| 405 | Method Not Allowed | 客户端请求中的方法被禁止 |
| 406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
| 407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
| 408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
| 409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
| 410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
| 411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
| 412 | Precondition Failed | 客户端请求信息的先决条件错误 |
| 413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
| 414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
| 415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
| 416 | Requested range not satisfiable | 客户端请求的范围无效 |
| 417 | Expectation Failed(预期失败) | 服务器无法满足请求头中 Expect 字段指定的预期行为。 |
| 418 | I'm a teapot | 状态码 418 实际上是一个愚人节玩笑。它在 RFC 2324 中定义,该 RFC 是一个关于超文本咖啡壶控制协议(HTCPCP)的笑话文件。在这个笑话中,418 状态码是作为一个玩笑加入到 HTTP 协议中的。 |
| 500 | Internal Server Error | 服务器内部错误,无法完成请求 |
| 501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
| 502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
| 503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
| 504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
| 505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的 |
依赖模块
pip install requests
requests库
Requests库由Kenneth Reitz于2012年创建,旨在简化HTTP客户端的使用。它建立在urllib3之上,requests库语法简洁方便、设计哲学遵循PEP20。但提供了更加人性化的接口和丰富的功能特性。只需一行代码,就能发起HTTP GET、POST等各类请求,并能自动处理各种HTTP认证机制、重定向、cookies以及超时等问题。
响应内容有字符串和字节两种,图片就是字节
响应头是很少用的(response.headers),除非是Cookie信息
一般都可以通过响应拿到对应的请求,response.request
import requests
web_url = 'http://angelimg.spbeen.com'
image_url = 'http://image.angelimg.spbeen.com/00000mx00000/plxMqNPh8STyjOqmr4FT278189/t6wBxv26jqIMCiNXDBWH278189-fwork5.jpg'
json_url = 'http://ncovdata.spbeen.com/apis/get_china_provinces/?query_date=2020-01-31'
# 字典里面是键值对 Cookie
headers = {
'User-Agent': 'husong_xxxxx',
'Referer': web_url,
}
response = requests.get(url=web_url, headers=headers) # get请求网页 post提交数据
# 1 <Response [200]>
print(1, response)
# 2 {'Server': 'openresty/1.21.4.1', 'Date': 'Thu, 12 Dec 2024 01:30:29 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '25516', 'Connection': 'keep-alive', 'X-Frame-Options': 'SAMEORIGIN'}
print(2, response.headers)
# 3 200
print(3, response.status_code)
#4 content字节 (视频、图片、文件)
print(4, response.content)
# 5 text内容字符串 带换行(仅限于文字)
print(5, response.text)
# print(5, type(response.text), response.text)
# print(6, type(response.json()), response.json())
open函数
为本文件的写入是在最后直接写入,注意写入换行
文本文件需要编码格式,推荐编码是UTF-8
图片在响应中是字节,文件写入也要用字节(wb)
文件名称要从url中分割,注意字符串切割方式
防止文件重复的常规操作:拼接固定长度的任意字符串
| 解释 | 说明 | |
|---|---|---|
| 参数形式 | open(filename,mode,encoding) | open函数写入模式会覆盖,追加模式不会覆盖 |
| mode | r、w、rb(二进制读)、wb(二进制写)、a(追加) | |
import requests
from lxml import etree
url = 'http://angelimg.spbeen.com/'
headers = {
"Referer": 'http://angelimg.spbeen.com/'
}
response = requests.get(url)
html = etree.HTML(response.text)
# html标签下所有img标签的href属性
image_links = html.xpath('.//img/@src')
# 图片链接地址保存到文件中(打开一次文件,多次写入)
with open('images.txt', mode='w', encoding='utf8') as file:
for link in image_links:
file.write(link)
file.write('\n')
# 图片下载
for index, link in enumerate (image_links, start=1):
# 列表里面最后一个元素[-1] GalvNQOQn1GDorXv4jTM281185-aKteOG.png
imagename = link.split('/')[-1]
# 1_TxptpmUqJry0KotkvHqc306662-5ogO4w.jpg
filename = "image/{}_{}".format(index,imagename)
try:
# 下载图片基于url
image_response = requests.get(link,headers=headers)
# as 能防止内存溢出,在每次程序执行完之后会释放内存,将执行结果保存到image变量中
with open(filename,mode='wb') as image:
image.write(image_response.content)
print(index,'success',link)
except:
print(index,'error',link)
请求头字段
| 请求头字段 | 功能描述 | 备注 |
|---|---|---|
| User-Agent | 用户代理信息,含操作系统、内核、浏览器等 | |
| Referer | 请求的来源信息,经常变动 | 列表2来自于列表1,列表3来自于列表2 |
| Accept-xxx | 接受的信息,如:编码、文件内容、格式等 | 98%的站点都对此字段没有要求 |
| Authorization | 请求头中常见的认证信息之一 | 用于前后端分离场景,token |
| Host | 请求网址的域名,少用于请求验证 | |
| 目标网址 | 请求的目的地 | 协议://域名/路径 |
| 请求方法 | get、post、put、delete等 | |
| Cookie | Cookie本身是一个集合,放在请求头header中,它可以包含很多信息,常用于用户登录后的字段、广告联盟字段信息 | 请求库默认会自动的处理和使用Cookie,例如Scrapy |
响应头字段
| 请求头字段 | 功能描述 | 备注 |
|---|---|---|
| 响应状态码 | 200、302、404、500 | |
| 响应内容 | response.content、response.text、response.json | |
| Server | 具体的网络服务器软件,用于接收和转发,如:nginx、apche | |
| Set-Cookie | 用于回复浏览器,让浏览器设置的cookie字段信息,包含cookie的名称、值、路径、有限期 | 浏览器、Scrapy默认支持,Set- |
| Content-Type | 响应内容的类型,如:image/png;text/txt;video/mp4 |
请求构造
保证数据传输的准确性和完整性
提高数据传输的效率,保证数据的安全性
实现客户端和服务器之间的交互
| 请求方式 | 功能描述 | 数据传输 | 安全性 |
|---|---|---|---|
| get | 主要用于获取服务器上的资源,如:看网页、查数据。 | URL传参,以?符号开头,用&符号拼接,可见 | 一班参数直接拼接在URL中,不适用敏感数据 |
| post | 主要用于向服务器提交数据,如:登录、上传资料 | 通过HTTP主体传参,隐藏在HTTP内部,不可见,对数据量大小没有限制 | 放在HTTP主体中,相对安全,可以对主题数据进行加密,适合敏感数据 |
显性参数
a.com/path?name=lisi&passwd=lisimima
import requests
from lxml import etree
url = 'http://angelimg.spbeen.com/'
headers = {
'Referer': url,
'Host': 'angelimg.spbeen.com',
}
cookies = {}
params = {
'key1': 'value1',
'key2': 'value2',
}
response = requests.get(url,headers=headers, params=params)
request = response.request
print(response,request)
# {'User-Agent': 'python-requests/2.32.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Referer': 'http://angelimg.spbeen.com/', 'Host': 'angelimg.spbeen.com'}
print(request.headers)
# http://angelimg.spbeen.com/?key1=value1&key2=value2
print(request.url)
隐性参数
隐性参数是放在请求主体中的,有推荐格式,没有固定格式
推荐格式:json字符串、jQuery回调格式等
爬虫需要按照HTTP包格式,制作特定格式和服务器通讯
POST提交数据
JSON格式的字符串数据
加密数据,如:登录操作的密码,需要密文传输
纯文本数据(多用于get)
import requests
from lxml import etree
import json
url = 'http://angelimg.spbeen.com/'
headers = {
'Referer': url,
'Host': 'angelimg.spbeen.com',
}
data = {
'key1': 'value1',
'key2': 'value2',
}
# response = requests.post(url,headers=headers,data=data)
response = requests.post(url,headers=headers,data=json.dumps(data))
request = response.request
#<Response [403]> <PreparedRequest [POST]>
print(response,request)
# {'User-Agent': 'python-requests/2.32.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Referer': 'http://angelimg.spbeen.com/', 'Host': 'angelimg.spbeen.com', 'Content-Length': '23', 'Content-Type': 'application/x-www-form-urlencoded'}
print(request.headers)
# http://angelimg.spbeen.com/
print(request.url)
# {"key1": "value1", "key2": "value2"}
print(request.body)
SSL证书
SSL证书是一种安全协议,为网上的数据传输提供安全保障(HTTPS)。
SSL能有效防止钓鱼网站及中间人攻击,确保网站安全。
爬虫请求https协议链接,需要验证一次ssI证书,请求时设置不验证证书,安全性有欠缺,代码 verify=False
提前安装pyopenssl后,请求可以自动验证证书(爬虫端自动验证)
安装pyopenssl
pip install pyopenssl --upgrade
示例代码
import requests
url = 'https://www.baidu.com/'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
}
# 1. pip install pyopenssl --upgrade
response = requests.get(url, headers=headers)
print(response, response.text)
# 2. verify=False
# response = requests.get(url, headers=headers, verify=False)
# print(response, response.text)
HTML、TXT、JSON
都是文本形式的字符串
TXT文本不依赖于任何定义的标记语法或变成语言
在Python中都是字符串的形式进行存储
三者都可用于存储和传输数据的类型格式
json 在python中就是json格式的字符串,双引号""
dict 是单引号 ''
也不能以 单引号和双引号来判断类型,还是以type()函数进行判断为准
import json
dict_a = {
'name' : 'husong',
'age' : 18,
'job' : 'software engineer'
}
# <class 'dict'> {'name': 'husong', 'age': 18, 'job': 'software engineer'}
print (type(dict_a),dict_a)
# dict => json
# <class 'str'> {"name": "husong", "age": 18, "job": "software engineer"}
json_a = json.dumps(dict_a)
print(type(json_a),json_a)
# json => dict
# <class 'dict'> {'name': 'husong', 'age': 18, 'job': 'software engineer'}
dict_json_a = json.loads(json_a)
print(type(dict_json_a),dict_json_a)
安装ipython
python -m pip install --upgrade pip
pip install ipython
Selenium
Selenium自动化测试框架介绍和原理
-
Selenium自动化测试框架
- 是一个用于Web应用程序测试的自动化测试工具
- 支持多种浏览器(如Chrome、Firefox、Safari等)和操作系统(如Windows、Linux、macOS等),
- 提供了一个丰富的API,允许用户模拟真实用户的交互行为,如点击、输入、提交等
- 官网:https://www.selenium.dev
- 特点
- 跨平台与多浏览器支持:Selenium可以在不同的操作系统和浏览器上运行,提供了广泛的兼容性。
- 开源与免费:Selenium是一个开源项目,用户可以免费地使用其中的工具和库。
- 强大的API:Selenium提供了丰富的API,允许用户进行复杂的页面操作和验证。
- 支持自动化测试:Selenium可以模拟真实用户的交互行为,从而进行自动化测试,提高测试效率。
- 集成与扩展性:可以与其他测试工具(如Jenkins)和编程语言(如Python、Java等)集成,提供了很好的扩展性。
- 优点:
- 灵活性:Selenium提供了丰富的API,用户可以根据自己的需求进行定制化的测试。
- 开源与免费:Selenium是一个开源项目,用户可以免费地使用其中的工具和库,降低了测试成本。
- 跨平台与多浏览器支持:Selenium可以在不同的操作系统和浏览器上运行,提供了广泛的兼容性。
- 缺点:
- 学习曲线陡峭:对于初学者来说,Selenium的学习成本可能较高,需要掌握一定的编程和测试知识。
- 性能问题:由于Selenium模拟真实用户的交互行为,所以相比于传统的性能测试工具,其执行速度可能较慢。
- 对页面结构敏感:Selenium的脚本编写通常依赖于页面的DOM结构,如果页面结构发生变化,需要相应地更新测试脚本
-
Selenium框架的原理
- 通过WebDriver与浏览器进行交互,WebDriver是一个API和协议,定义了用于控制Web浏览器行为的接口。
- Selenium通过调用WebDriver接口方法完成对浏览器的操作,如点击、输入、提交等。
- 这些操作都通过HTTP请求发送给浏览器驱动,浏览器驱动再转发给浏览器执行。

-
Selenium框架的使用步骤
- 环境搭建(最多人踩坑的地方-浏览器版本和驱动版本对应关系)
- 安装Selenium库,不同语言采用不同依赖包安装
- 下载与浏览器对应的WebDriver,如ChromeDriver或GeckoDriver,并确保它们与浏览器版本匹配。
- 【强调三遍!!!!】浏览器版本一定要和驱动版本一一对应,不然存在不兼容问题
- 编写测试脚本
- 使用Selenium提供的API编写测试脚本
- 通常包括启动WebDriver、打开浏览器、执行用户请求(如点击按钮、填写表单等)、获取页面元素等操作。
- 运行测试脚本
- 将脚本文件与WebDriver和浏览器一起运行,以执行自动化测试。
- 分析结果
- 根据测试结果分析潜在的问题和缺陷,并进行相应的修复和优化
- 环境搭建(最多人踩坑的地方-浏览器版本和驱动版本对应关系)
安装Selenium
Selenium的3版本和4版本语法不通用,推荐4版本
# 单个Python版本这样安装
pip install selenium==4.5
# 多个Python版本这样安装
python3 -m pip install selenium==4.5
#更新 selenium
pip install --upgrade selenium
导入
# 进入交互模式
ipython
# 导包
from selenium import webdriver
# 运行
chrome = webdriver.Chrome()


下载chromedriver
查看浏览器版本

下载chromedriver

添加到PATH环境变量中

网页元素的5种定位方式
| 定位方式 | JS语法 | lxml的XPATH语法 |
|---|---|---|
| 根据id定位 | find_element(By.ID, 'idname') | .//*[@id] |
| 根据名称定位 | find_element(By.TAG_NAME, 'tagname') | |
| 根据类名定位 | find_element(By.CLASS_NAME, 'class') | .//*[@class="remind_warp"] |
| 根据xpath规则定位 | find_element(By.XPATH, 'xpath') | .//*[contains(@class,"l")] |
| 根据链接文本定位 | find_element(By.LINK_TEXT, 'text') |
find_element[s]能查找元素属性或值只能定位标签元素,不能查找元素属性或值
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
url = 'https://www.imooc.com'
driver = webdriver.Chrome()
driver.get(url)
sleep(2)
## 5种定位元素的方式
# By.ID
result1 = driver.find_element(By.ID, 'globalTopBanner')
print("result1",result1)
# By.CLASS_NAME
result2 = driver.find_elements(By.CLASS_NAME, 'remind_warp')
print("result2",result2)
# By.TAG_NAME
result3 = driver.find_elements(By.TAG_NAME, 'html')
print("result3",len(result3),result3)
# By.XPATH
result4 = driver.find_elements(By.XPATH, './/*[contains(@class,"l")]')
print("result4",len(result4))
# By.XPATH
result5 = driver.find_elements(By.LINK_TEXT, '免费课')
print("result5",len(result5),result5)
driver.quit()
元素点击操作
| 定位方式 | 语法 | 备注 |
|---|---|---|
| 通过元素点击 | element.click | |
| 返回上一个页面 | driver.back() |
要操作的元素标签不能被遮挡,元素点击操作是瞬间的,控制权会立即回到Python代码,为了给浏览器足够的响应时间,尽可能在操作后加睡眠时间。
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
url = 'https://www.imooc.com'
driver = webdriver.Chrome()
driver.get(url)
sleep(2)
input_element = driver.find_element(By.CLASS_NAME, 'nav-search-input')
input_element.clear()
input_element.send_keys("Java")
sleep(2)
search_element = driver.find_element(By.CLASS_NAME, 'hotTags')
search_element.click()
sleep(3)
driver.back()
sleep(6)
driver.quit()
获取网页数据内容
| 定位方式 | 语法 | 备注 |
|---|---|---|
| 获取标签 | find_element() | 支持链式操作 |
| 获取标签属性 | element.get_attribute(xxxx) | |
| 获取标签文本 | element.text |
先定位标签,再获取标签文本
可以通过标签获取属性,但是无法通过属性定位标签
在爬虫代码中,推荐使用字典封装一组数据
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
url = 'https://www.imooc.com'
driver = webdriver.Chrome()
driver.get(url)
# 获取新课内容
# 1 {'title': 'AI助手Copilot辅助Go+Flutter打造全栈式在线教育系统', 'number': '初级 · 52人报名', 'price': '¥499.00'}
course_list_a = driver.find_elements(By.XPATH, '//*[@id="main"]/div[3]/div/div[1]/div[1]/div/a')
print(len(course_list_a),course_list_a)
for index, element in enumerate(course_list_a,start=1):
item = {}
item['title'] = element.get_attribute('data-title')
item['number'] = element.find_element(By.CLASS_NAME,'difficulty').text
item['price'] = element.find_element(By.CLASS_NAME,'price').text
print(index,item)
sleep(5)
driver.quit()
Selenium无窗口模式
优势:隐藏界面,不会抢占聚焦状态后台执行且不报错(服务器一般都是没有界面的)
推荐分辨率:add_argument("--window-size=1920,1080")
代码:chrome_optios.add_argument('--headless')
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from time import sleep
url = 'https://www.imooc.com'
opt = Options()
opt.add_argument('--headless')
opt.add_argument('--disable-gpu')
opt.add_argument('--window-size=3440,1440')
driver = webdriver.Chrome(options=opt)
browser = webdriver.Chrome(options=opt)
browser.set_window_size(3440, 1440)
# driver = webdriver.Chrome()
driver.get(url)
sleep(2)
# 获取新课内容 1 {'title': 'AI助手Copilot辅助Go+Flutter打造全栈式在线教育系统', 'number': '初级 · 52人报名', 'price': '¥499.00'}
course_list_a = driver.find_elements(By.XPATH, '//*[@id="main"]/div[3]/div/div[1]/div[1]/div/a')
print(len(course_list_a),course_list_a)
for index, element in enumerate(course_list_a,start=1):
item = {}
item['title'] = element.get_attribute('data-title')
item['number'] = element.find_element(By.CLASS_NAME,'difficulty').text
item['price'] = element.find_element(By.CLASS_NAME,'price').text
print(index,item)
sleep(5)
driver.quit()
Selenium浏览器的懒加载
爬虫加速策略:看html内容,不需要看图片,所以不需要加载图片。
单个网页的常见大小是1~10MB
单个网页的数据部分的大小是500~2000KB左右
单个网页的图片部分的大小是3~6MB左右
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from time import sleep
url = 'https://www.imooc.com'
chrome_options = Options()
# 不加载图片
prefs = {'profile.managed_default_content_settings.images': 2}
chrome_options.add_experimental_option('prefs', prefs)
driver = webdriver.Chrome(chrome_options=chrome_options)
driver.get(url)
sleep(5)
driver.quit()

Selenium执行JS代码
Selenium本质就是浏览器
浏览器本身支持JavaScript代码
特殊需求下,原生js会比python代码方便得多
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from time import sleep
url = 'https://www.imooc.com'
chrome_options = Options()
# 不加载图片
prefs = {'profile.managed_default_content_settings.images': 2}
chrome_options.add_experimental_option('prefs', prefs)
driver = webdriver.Chrome(chrome_options=chrome_options)
driver.get(url)
sleep(2)
# js控制代码
# 获取网页的高度
html_tag = driver.find_element(By.TAG_NAME,'html')
height = html_tag.size['height']
print('html height',height)
for i in range(0, height, 10):
js_script = f'window.scrollBy(0,{i})'
# js_script = 'window.scrollBy(0,{})'.format(i)
driver.execute_script(js_script)
sleep(0.01)
sleep(5)
driver.quit()
新建浏览器标签页
标签页的新建方式
- 原网页中的链接,通过点击(a标签)直接创建了新的标签页
target="_blank" - 手动新建标签页,输入网址并访问
window.open("https://www.imooc.com")
多个标签页,光标还是在第一个标签页
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
url = 'https://www.imooc.com'
driver = webdriver.Chrome()
driver.get(url)
sleep(3)
# 点击页面元素创建标签页
a_element = driver.find_element(By.XPATH, './/div[@class="system-class-show"]/a[@data-position="0"]')
print(a_element)
a_element.click()
sleep(3)
# 手动新建标签页,输入网址并访问
js_script = 'window.open("https://www.baidu.com")'
driver.execute_script(js_script)
sleep(3)
# 手动新建标签页,从元素中获取网址并访问
js_script_two = 'window.open("{}")'.format(a_element.get_attribute('href'))
driver.execute_script(js_script_two)
sleep(5)
driver.quit()
标签页的切换(光标)
每个标签页都是一个进程,都有自己的handle字符串(唯一标识)
一个浏览器可以创建很多个标签页
| 动作 | 语法 |
|---|---|
| 切换 | driver.switch_to.window() |
| 关闭(一个) | driver.close() |
| 退出(所有) | driver.quit() |
建议
- 一个浏览器driver中推荐1-2个标签页
- 建议使用多个浏览器driver
- 不推荐单个浏览器driver创建多个标签页
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
url = 'https://www.imooc.com'
driver = webdriver.Chrome()
driver.get(url)
sleep(3)
# 点击页面元素创建标签页
a_element = driver.find_element(By.XPATH, './/div[@class="system-class-show"]/a[@data-position="0"]')
print(a_element)
a_element.click()
sleep(3)
# 手动新建标签页,输入网址并访问
js_script = 'window.open("https://www.baidu.com")'
driver.execute_script(js_script)
sleep(3)
# 手动新建标签页,从元素中获取网址并访问
js_script_two = 'window.open("{}")'.format(a_element.get_attribute('href'))
driver.execute_script(js_script_two)
print(driver.current_window_handle)
# 打印所有标签页
for index, window in enumerate(driver.window_handles, start=1):
driver.switch_to.window(window)
print(index,'当前窗口: ', driver.current_window_handle)
sleep(2)
sleep(5)
driver.quit()
控制台输出
4F43B986824FAE85A32AEF508EA5484A
1 当前窗口: 4F43B986824FAE85A32AEF508EA5484A
2 当前窗口: 0C741B1D3F5EA3C477FFCA0B40345F7E
3 当前窗口: 8B8E25E9A3476BD92550C73F1428C501
4 当前窗口: 57317838B7DD784C6F2488ADB4B091CE
Selenium实战——爬取B站
数据格式要求说明
- 每个视频之间相互独立,互不影响
- 视频的增量数据要关联时间
- 单个视频的增量数据要按固定格式保存和追加
数据结构树说明
- 共三层结构,顶层为目标文件夹,例:videodata
- 中层的文件夹为视频标识,例:BV1XXXXXXX
- 底层文件为数据的载体,txt文本文件,用具体日期命名,
字典 => JSON字符串
需求一:Selenium自动化爬取B站数据(首页的部分数据)
- Selenium自动访问bilibili首页
- 爬取首页顶部的热门视频数据
- 设计数据格式,并将数据存储到本地
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from time import sleep
import os
import json
import datetime
def write_bili_video_data(item):
print(item)
source_dir = 'video_data'
target_dir = os.path.join(source_dir,item['video_id'])
if not os.path.exists(target_dir):
os.mkdir(target_dir)
file_name = "{}.txt".format(datetime.datetime.now().strftime("%Y%m%d"))
file_path =os.path.join(target_dir,file_name)
# 文本r w 二进制rb wb 追加a(如果源文件存在,则追加数据)
with open(file_path,mode='a', encoding='utf8') as file:
# 字典转为json字符串
file.write(json.dumps(item,ensure_ascii=False))
# 指针换行
file.write('\n')
def main():
url = 'https://www.bilibili.com/'
chrome_options = Options()
# 不加载图片
chrome_options.add_argument('--headless')
chrome_options.add_argument("--window-size=3840,2160")
prefs = {'profile.managed_default_content_settings.images': 2}
chrome_options.add_experimental_option('prefs', prefs)
driver = webdriver.Chrome(chrome_options=chrome_options)
driver.get(url)
feed_card = driver.find_elements(By.XPATH, '//div[@class="container is-version8"]//div[@class="feed-card"]')
print(len(feed_card))
for card in feed_card:
item = {}
item['title'] = card.find_element(By.XPATH, './/picture[@class="v-img bili-video-card__cover"]//img').get_attribute('alt')
item['link'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]').get_attribute('href')
item['video_id'] = item['link'].split('/')[-1]
item['play_time'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]//div[@class="bili-video-card__stats--left"]//span[1]//span').text
item['comments'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]//div[@class="bili-video-card__stats--left"]//span[2]//span').text
item['duration'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]//span[@class="bili-video-card__stats__duration"]').text
item['author'] = card.find_element(By.XPATH, './/span[@class="bili-video-card__info--author"]').get_attribute('title')
# 视频发布日期
item['date'] = card.find_element(By.XPATH, './/span[@class="bili-video-card__info--date"]').text
# 当期日期
item['datetime'] = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
# 写入文件
write_bili_video_data(item)
sleep(5)
driver.quit()
if __name__ == '__main__':
main()
需求二:Selenium增量爬取B站热门视频数据
- 读取本地的数据文件,获取视频信息等数据
- 通过Selenium访问视频地址,爬取最新数据
- 将数据按格式保存到本地的数据文件中
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from time import sleep
import os
import json
import datetime
def read_bili_video_data(driver):
source_dir = 'video_data'
# # 循环目录
# for x in os.walk(source_dir):
# ('video_data\\BV11swreRE7B', [], ['20250124.txt']) BV11swreRE7B下没有文件夹了,所以是[]空
# 列出source_dir目录下的所有目录
for dir in os.listdir(source_dir):
dir_path = os.path.join(source_dir,dir)
# video_data\BV11swreRE7B
print(dir_path)
# break
# 解析视频网址url
item = parse_video_url_info(driver,dir)
# 存储视频信息
write_bili_video_data(item)
def write_bili_video_data(item):
source_dir = 'video_data'
target_dir = os.path.join(source_dir,item['video_id'])
if not os.path.exists(target_dir):
os.mkdir(target_dir)
file_name = "{}.txt".format(datetime.datetime.now().strftime("%Y%m%d"))
file_path =os.path.join(target_dir,file_name)
# 文本r w 二进制rb wb 追加a(如果源文件存在,则追加数据)
with open(file_path,mode='a', encoding='utf8') as file:
# 字典转为json字符串
file.write(json.dumps(item,ensure_ascii=False))
# 指针换行
file.write('\n')
def parse_video_url_info(driver,video_id):
url = 'https://www.bilibili.com/video/{}/'.format(video_id)
# chrome_options = Options()
# # 不加载图片
# #chrome_options.add_argument('--headless')
# chrome_options.add_argument("--window-size=3840,2160")
# prefs = {'profile.managed_default_content_settings.images': 2}
# chrome_options.add_experimental_option('prefs', prefs)
# driver = webdriver.Chrome(chrome_options=chrome_options)
driver.get(url)
sleep(3)
item = {}
item['title'] = driver.find_element(By.XPATH,'.//h1[@class="video-title special-text-indent"]').get_attribute('title')
item['link'] = url
item['video_id'] = video_id
item['play_time'] = driver.find_element(By.XPATH,'.//div[@class="view-text"]').text
item['comments'] = driver.find_element(By.XPATH,'.//div[@class="dm-text"]').text
item['duration'] = driver.find_element(By.XPATH,'.//span[@class="bpx-player-ctrl-time-duration"]').text
item['author'] = driver.find_element(By.XPATH,'.//div[@class="up-detail-top"]/a[1]').text
# 视频发布日期
item['date'] = driver.find_element(By.XPATH,'.//div[@class="pubdate-ip-text"]').text
# 当期日期
item['datetime'] = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
print(item)
sleep(1)
return item
# feed_card = driver.find_elements(By.XPATH, '//div[@class="container is-version8"]//div[@class="feed-card"]')
# print(len(feed_card))
# for card in feed_card:
# item = {}
# item['title'] = card.find_element(By.XPATH, './/picture[@class="v-img bili-video-card__cover"]//img').get_attribute('alt')
# item['link'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]').get_attribute('href')
# item['video_id'] = item['link'].split('/')[-1]
# item['play_time'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]//div[@class="bili-video-card__stats--left"]//span[1]//span').text
# item['comments'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]//div[@class="bili-video-card__stats--left"]//span[2]//span').text
# item['duration'] = card.find_element(By.XPATH, './/a[@class="bili-video-card__image--link"]//span[@class="bili-video-card__stats__duration"]').text
# item['author'] = card.find_element(By.XPATH, './/span[@class="bili-video-card__info--author"]').get_attribute('title')
# # 视频发布日期
# item['date'] = card.find_element(By.XPATH, './/span[@class="bili-video-card__info--date"]').text
# # 当期日期
# item['datetime'] = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
# # 写入文件
# write_bili_video_data(item)
if __name__ == '__main__':
chrome_options = Options()
# 不加载图片
# chrome_options.add_argument('--headless')
#chrome_options.add_argument("--window-size=3840,2160")
prefs = {'profile.managed_default_content_settings.images': 2}
chrome_options.add_experimental_option('prefs', prefs)
driver = webdriver.Chrome(chrome_options=chrome_options)
# 读取各目录下文件名
read_bili_video_data(driver)
driver.quit()
浏览器渲染网页的完整流程
浏览器爬虫
高难度反爬场景,适合浏览器爬虫
需求明确的简单任务,适合用浏览器爬虫快速实现
- Python代码+浏览器的组合,环境中必须有浏览器程序
- Python操控浏览器,代码简单,浏览器自适应能力强
- 浏览器加载的页面复杂,运行速度慢,不适合大并发
脚本爬虫
大批量的数据,需要使用分布式脚本爬虫
- 纯Python代码,需要支持HTTP协议的库(Requests),不需要浏览器
- 脚本代码针对某个纯页面、或者某个接口专门开发
- 脚本代码的目的明确,速度更快(只需一个请求,不需要像浏览器一样几十个请求),适合批量的抓取
网页的访问顺序
- 浏览器请求某网域名对应的IP
- 浏览器请求网址对应IP,并拿到网址对应的网页内容
网页的结构顺序
- 网页的标记语法是HTML,HTML包含head和body标签
- head标签里标明了很多网页信息,例如静态资源链接
- body是网页的显示主体,包含网页内容和js代码
网页的加载顺序
- 第一次请求加载htm网页,里面写了图片、css等资源网址
- 多并发异步请求htm中其他的资源,如图片、css等
- 执行js代码,请求资源或者计算并渲染html代码
异步数据
- 不在第一次加载的html中的数据,都是异步数据
- 异步数据是is二次请求的数据,部分过程是肉眼可见的
- 异步数据开发出来专门优化网页加载慢的方法,例如:
网页下滑后出现的数据,每次点击页面变换的数据,数据在js代码中(js将数据渲染成html结构)
异步数据的分类
异步请求的数据:需要网络和服务交互
异步渲染的数据:数据在js中,执行后渲染成html
浏览器分析调试工具
常用的定位工具
- 分析网页结构:Element栏(元素栏),网页整体的html内容
- 分析网络请求:Network栏(网络栏),网络请求历史的记录窗口
- 调试JS代码:Console栏(控制栏),js
Elements中需要用到的技术
- XPATH:html搜索框中支持的搜索规则(仅支持HTML标签)
- 正则表达式:html搜索框中支持的搜索规则(文本都能检索)
Network中需要用到的技术
- 保留记录:保留请求记录,方便网页跳转间的记录查看

- 调速功能:限制加载速度,仔细观看网页的加载变化

- 分类功能:请求分类,并按特定分类显示要查找的内容

分析网络请求
- 打开Preserve Log,保留之前的记录
- 认识DOC(网页)、JS、CSS、XHR(异步请求的数据,JS服务服务器通信的数据包)、IMG(媒体文件)
- 多加练习搜索框,每个框体都有搜索框
数据的常用位置
- 请求头数据
- 响应主体数据
通过浏览器分析网络请求
- 网页数据的内容查找
- 接口数据的查找
- 网页JS代码中的数据查找


异步渲染
- 异步渲染,是指JS代码执行,将数据渲染成HTML代码
- 数据在网页上,但是不是HTML,而是JS代码中
- 使用正则表达式,将JS中的数据内容提取出来(XPATH是专门解析HTML的,JS中的数据需要正则表达式进行提取)
正则表达式说明
- 虽然都支持正则表达式,但是支持的程度都是不同的
- 正则表达式的编写和测试,可以搜索“在线正则表达式
- 正则表达式重在表达,越简单越好,推荐一次数据一步到位
import requests
import re
url = 'https://www.bilibili.com/'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'}
response = requests.get(url, headers=headers)
print(response.text)
# 解析查询结果
# result = re.findall('"response":{.*?}', response.text)
result = re.findall('window.__pinia=\(.*\)',response.text)
print(result)
获取异步请求数据
- 异步接口地址:目标地址
- 异步接口参数:请求时携带的参数
- 异步数据结构:读取并解析数据格式
- 异步数据的结构不是固定的,json居多
- 异步数据的难点在于请求参数
- 尽可能多的解读请求参数
import requests
import re
url = 'https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd?web_location=1430650&y_num=4&fresh_type=4&feed_version=V8&fresh_idx_1h=1&fetch_row=4&fresh_idx=1&brush=1&homepage_ver=1&ps=12&last_y_num=5&screen=1718-537&seo_info=&last_showlist=av_113894697734248,av_113889765229174,av_113870152728910,av_113872753201496,ad_5614_113508838605115,av_113883691881507,av_n_113887064101557,av_n_113887298979181,av_n_113878474163078,av_n_113879229142076&uniq_id=377069741668&w_rid=44900793389e870219dff6ac637139ba&wts=1737949968'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'}
response = requests.get(url, headers=headers)
print(response.text)
# 解析查询结果
# result = re.findall('"response":{.*?}', response.text)
# result = re.findall('window.__pinia=\(.*\)',response.text)
# print(result)
response_data = response.json()
# data = response_data('data', {}) 容错处理
data = response_data['data']
print(data)
for item in data.get('item',[]):
print(item)
异步数据的数据格式与解析
异步数据的数据格式
- 前后端数据交互,多数用json格式
- 少量内容交互,常见的200、success等
- 多个值非json格式的数据传递 x=a&y=b&z=C
异步数据的解析
- json格式:import json
- 纯文本数据传递;import re
- 非规范结构:str.replace()&str.split()