网络爬虫开发
初识爬虫
1.requests请求库模块
Pycharm终端安装requests请求库
pip install requests
①.导入requests请求库
import requests
②.定义个变量要抓取的页面:
url = '网址'
③.构造请求头(伪装身份user-agent)
打开要抓取的网址,快捷键:F12→Network(网络)→Name(名称)→点一个文件→Header(标头)→user-agent(拉到底部就会看到)
#伪装成浏览器的身份进行抓取 headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' }
④.发起请求并接收响应对象
response = requests.get(url = url, headers = headers) encoding = 'utf-8'
bd_url = 'https://www.baidu.com/' # 定义要请求的url数据包 response = requests.get(url=bd_url) # 发起请求并且接收响应对象 response.encoding = 'utf-8' # 设置编码格式 print(response) # <Response [200]> 响应对象 print(response.text) # 获取文本内容 print(response.content) # 获取到进制格式的数据 print(response.headers) # 获取响应头信息 print(response.request.headers) # 获取请求头信息 print(response.status_code) # 获取状态码 print(response.encoding) # 获取当前编码格式 print(response.url) # 获取到当前请求的url print(response.cookies) # 获取到用户身份对象 print(response.content.decode()) # .decode() 默认utf-8 进行解码 如果需要使用其他编码进行解码 传参即可
⑤.保存文件然后读取
#完整示例-爬图 import requests url = 'https://pic.netbian.com/uploads/allimg/240924/081942-17271371826661.jpg' response = requests.get(url=url) # print(response.content) with open('王楚然.jpg', 'wb') as f: f.write(response.content) #完整示例-爬音频 import requests url = 'https://m701.music.126.net/20240924221116/8ec6067b2ae956d58df4deff0bdddaf9/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/31433228192/7100/62f1/fd29/3833d333ff983ce8eebe35872a9484ee.m4a' response = requests.get(url=url) with open('大人中.mp3', 'wb') as f: f.write(response.content) #爬视频 import requests cookies = { 'PEAR_UUID': '7b913605-1037-4a28-b16a-46e7657e6f03', 'Hm_lvt_9707bc8d5f6bba210e7218b8496f076a': '1727185861', 'HMACCOUNT': '33EE743B756BCDEE', 'Hm_lpvt_9707bc8d5f6bba210e7218b8496f076a': '1727185870', } headers = { 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', # 'cookie': 'PEAR_UUID=7b913605-1037-4a28-b16a-46e7657e6f03; Hm_lvt_9707bc8d5f6bba210e7218b8496f076a=1727185861; HMACCOUNT=33EE743B756BCDEE; Hm_lpvt_9707bc8d5f6bba210e7218b8496f076a=1727185870', 'pragma': 'no-cache', 'priority': 'i', 'range': 'bytes=0-', 'referer': 'https://www.pearvideo.com/', 'sec-ch-ua': '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'video', 'sec-fetch-mode': 'no-cors', 'sec-fetch-site': 'same-site', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', } response = requests.get( 'https://video.pearvideo.com/mp4/short/20171004/cont-1165391-10964201-hd.mp4', cookies=cookies, headers=headers, ) print(response.content) with open('1.mp4', 'wb') as f: f.write(response.content)
Pycharm内置模块方法(不推荐使用)
from urllib.request import urlopen url ='url'#要爬取的数据网页 html = urlopen(url) #print(html.read().decode('utf-8')) with open("爬取的文件.html", "w",encoding="utf-8") as f: write = f.write(html.read().decode('utf-8'))
★用input方法的get请求
import requests from urllib.parse import quote import time try: # 获取用户输入并进行URL编码 content = input("请输入你要查询的内容:") encoded_content = quote(content) # 构建请求URL url = f"https://www.sogou.com/web?query={encoded_content}" # 设置请求头模拟浏览器 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" } # 发送HTTP请求 print(f"正在请求: {url}") resp = requests.get(url, headers=headers, timeout=10) # 设置超时时间 # 检查响应状态码 resp.raise_for_status() # 若状态码非200,抛出HTTPError # 保存响应内容到文件 filename = f"爬取的{content[:10]}内容.html" # 文件名包含部分查询内容 with open(filename, "w", encoding="utf-8") as f: f.write(resp.text) # 直接使用resp.text print(f"✅ 文件已成功保存为: {filename}") print(f"状态码: {resp.status_code}") except requests.exceptions.HTTPError as e: print(f"HTTP请求错误: {e}") except requests.exceptions.Timeout: print("请求超时,请重试") except requests.exceptions.RequestException as e: print(f"网络请求异常: {e}") except Exception as e: print(f"发生未知错误: {e}") finally: # 控制请求频率 time.sleep(1) # 等待1秒后结束程序
★用input方法的post请求
import requests from urllib.parse import urlencode import time import json try: # 获取用户输入 keyword = input("请输入你要翻译的内容:") # 百度翻译API的POST接口 url = "https://fanyi.baidu.com/sug" # 设置请求头 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded" } # 构建POST请求的表单数据(百度翻译API需要的参数) data = { "kw": keyword # 百度翻译API使用'kw'参数传递要翻译的内容 } # 发送POST请求 print(f"正在请求: {url}") resp = requests.post(url, headers=headers, data=data, timeout=10) #发送post请求时,传参数用的是data. # 检查响应状态码 resp.raise_for_status() # 解析JSON响应 result = resp.json() # 保存响应内容到文件 filename = f"翻译结果_{keyword[:10]}.json" with open(filename, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(f"✅ 翻译结果已保存为: {filename}") print(f"状态码: {resp.status_code}") # 简单打印翻译结果 if result.get('errno') == 0 and 'data' in result: print("\n翻译结果预览:") for item in result['data'][:3]: # 只显示前3个结果 print(f"{item.get('k', '')} -> {item.get('v', '')}") except requests.exceptions.HTTPError as e: print(f"HTTP请求错误: {e}") except requests.exceptions.Timeout: print("请求超时,请重试") except requests.exceptions.RequestException as e: print(f"网络请求异常: {e}") except json.JSONDecodeError: print("无法解析JSON响应,可能是请求格式不正确") except Exception as e: print(f"发生未知错误: {e}") finally: # 控制请求频率 time.sleep(1)
参数请求与Cookie
import requests url = "https://movie.douban.com/j/chart/top_list" #问号后面的参数可以用字典的形式传参数 data = { "type":"13", "interval_id":"100:90", "action": "", "start":"0", "limit":"20" } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded" } response = requests.get(url, params=data, headers=headers)#发送get请求时,传参数用的是params print(response.json())
在编程语言中使用 XPath
pycharm终端安装lxml库:
pip install lxml #或使用镜像下载安装 pip install lxml -i 镜像地址
pycharm终端查看当前环境下安装了哪些库
pip list
①.解析 XML 文档
2.2.etree.fromstring():可以将 XML/HTML 格式的字符串解析为树形结构(ElementTree 对象),以便于后续的遍历、查询和修改。
from lxml import etree xml_str = '<book><title>Python编程</title><price>99.0</price></book>' root = etree.fromstring(xml_str) # 获取根节点标签 print(root.tag) # 输出: book # 查找子节点 title = root.find('title').text price = root.find('price').text print(f"书名: {title}, 价格: {price}") # 输出: 书名: Python编程, 价格: 99.0
②.遍历元素
# 遍历所有子元素 for child in root: print(child.tag, child.text) # 输出: element 内容 # 使用XPath查找元素 elements = root.xpath('//element')
# 创建新元素 new_element = etree.Element('new_element') new_element.text = '新内容' # 添加到根节点 root.append(new_element) # 修改现有元素 root.find('element').text = '更新后的内容' # 删除元素 root.remove(new_element)
④.输出文档
# 转换为字符串 xml_output = etree.tostring(root, pretty_print=True, encoding='unicode') print(xml_output) # 写入文件 # etree.ElementTree(root).write('output.xml', pretty_print=True)
from lxml import etree #如果Pycharm报错可以考虑下面这种导入方式 from lxml imprt html etree=html.etree html = '<html><body><div class="content"><h1>Hello</h1></div></body></html>' tree = etree.HTML(html) # 选择所有<h1>元素的文本 titles = tree.xpath('//h1/text()') print(titles) # 输出: ['Hello'] # 选择class为"content"的<div>元素 divs = tree.xpath('//div[@class="content"]')
打开抓取的html文档然后读取
with open('抓取的文档.html', 'r', encoding='utf-8') as f: html = f.read() # 读取 # print(html) # 读取出来的文本内容 # print(type(html)) # <class 'str'>查看类开,字符串类型 tree = etree.HTML(html) # 渲染html
// 选择第一个<h1>元素 const h1 = document.evaluate('//h1', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; // 选择所有<a>元素的href属性 const links = document.evaluate('//a/@href', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0; i < links.snapshotLength; i++) { console.log(links.snapshotItem(i).value); }
①.基本路径表达式
xpath 通过路径表达式从根节点(/
)或当前节点(.
)开始定位元素:
xpath方法返回值是列表 xpath语法规定索引值从1开始
- 绝对路径:从根节点开始,使用
/
分隔节点名称。
xpath('/html/body/div/h1/') # 选择HTML文档中的<h1>元素 xpath('/html/body/div/h1/text()') # 选择HTML文档中的<h1>元素的数据
- 相对路径:从当前节点开始,使用
//
表示任意层级的子节点。
xpath('//div/p') # 选择所有<div>下的<p>元素(不限层级)
符号 | 作用 |
// | 从任意位置开始选择节点。 |
. | 选择当前节点。 |
.. | 选择当前节点的父节点。 |
@ | 选择属性。 |
* | 通配符,匹配任意节点或属性 |
[ ] | 条件表达式,用于筛选节点。 |
position | 当前节点的位置(如 [1] 表示第一个匹配的节点)。 |
③.常用函数
- 文本匹配:
xpath('//div[text()="Hello World"]') # 选择文本内容为"Hello World"的<div> xpath('//div[contains(text(), "World")]') # 选择包含"World"的<div>
属性匹配:
xpath('//a[@href="https://example.com"]') # 选择href属性为指定值的<a>标签 xpath('//input[@type="text"]') # 选择包含"World"的<div>
位置筛选:
xpath('//li[position()=1]') # 选择第一个<li> xpath('//li[last()]') # 选择最后一个<li>.<li>
使用 and
、or
、not()
组合条件:
xpath('//div[@class="container" and @id="main"]') # 同时满足两个属性条件 xpath('//div[@class="item" or @class="product"] ') # 满足任一属性条件 xpath('//div[not(@class="ignore")]') # 排除特定属性的<div>
轴用于选择当前节点的相关节点(如父节点、兄弟节点等):
轴名称 | 作用 | 示例 |
parent:: | 父节点 | xpath('//h1/parent::div') |
child:: | 子节点(默认) | xpath('//div/child::p') |
descendant | 所有后代节点(不限层级) | xpath('//div/descendant::span') |
ancestor:: | 所有祖先节点(包括父节点的父节点) | xpath('//p/ancestor::body') |
following-sibling:: | 后续兄弟节点 | xpath('//li[1]/following-sibling::li') |
preceding-sibling: | 前续兄弟节点 | xpath('//li[3]/preceding-sibling::li') |
案例:
# 导入所需库 import requests # 用于发送HTTP请求,获取网页内容 from lxml import etree # 用于解析HTML文档,提取需要的信息 # 定义基本参数 # 目标网页URL(小说章节页面) url = 'https://www.xs386.com/6661/32866513.html' # 请求头信息,模拟浏览器访问 headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' } # user-agent用于告诉服务器访问者的浏览器信息,避免被服务器识别为爬虫而拒绝访问。 # 发送GET请求获取网页内容 response = requests.get(url=url, headers=headers) # 设置正确的编码,设置编码为utf-8,确保中文显示正常 response.encoding = 'utf-8' # 将HTML文本解析为可操作的元素树对象 e = etree.HTML(response.text) # 将网页源代码转换为解析树,便于后续提取数据 # 使用XPath表达式提取id为"content"的div内的所有文本,注意返回的是列表 content_list = e.xpath('//div[@id="content"]//text()') # 查找整个文档中 id 属性为 "content" 的 div 元素并提取该 div 下所有层级的文本内容 # 判断是否成功提取到内容 if content_list: # 将列表内容合并为字符串 content = '\n'.join(content_list) # 清理内容中的多余空格和换行 content = content.strip() # 保存到文件(使用utf-8编码) with open('上门龙婿.txt', 'w', encoding='utf-8') as f: f.write(content) print("内容保存成功") else: print("未找到指定内容")
3.JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,以易于人阅读和编写的文本格式存储和表示数据,同时也易于机器解析和生成,在前后端数据交互、配置文件等场景中被广泛使用。
- 轻量级:相比 XML 等格式,JSON 的数据结构更简洁,文件体积更小,传输效率更高。
- 跨语言兼容:几乎所有编程语言都支持 JSON 的解析和生成,便于不同系统间的数据交互。
- 可读性强:采用键值对(key-value)结构,格式清晰,易于理解和维护。
- 结构灵活:支持嵌套对象和数组,能表示复杂的数据结构。
数据类型与表示
- 对象(Object):用
{}
包裹,由键值对组成,键和值用:
分隔,多个键值对用,
分隔。 - 数组(Array):用
[]
包裹,元素可以是任意数据类型,元素间用,
分隔。 - 键值对:键必须是字符串,值可以是字符串、数字、布尔值、null、对象或数组。
{ "id": 1001, "hobbies": ["阅读", "编程"], "isStudent": false, "address": null }
格式要求
- 键名必须用双引号
""
包裹(部分语言支持单引号,但标准 JSON 要求双引号)。 - 逗号分隔时,最后一个键值对后不能有多余的逗号(部分环境支持,但可能导致兼容性问题)。
// 示例:JSON与JavaScript对象的转换 const jsonStr = '{"name":"张三","age":25}'; const obj = JSON.parse(jsonStr); // 转换为对象 console.log(obj.name); // 输出:张三 const data = { city: "北京", population: 2100 }; const jsonData = JSON.stringify(data); // 转换为JSON字符串 console.log(jsonData); // 输出:{"city":"北京","population":2100}
- 前后端数据交互:Web 应用中,前端通过 API 请求从服务器获取 JSON 数据,或向服务器发送 JSON 格式的请求参数。
- 配置文件:如项目配置文件(
package.json
)、系统配置等,用 JSON 存储结构化配置信息。 - 数据存储:NoSQL 数据库(如 MongoDB)支持 JSON 格式的文档存储,部分关系型数据库也支持 JSON 字段。
- API 接口规范:RESTful API 通常以 JSON 作为数据交换格式,例如 OpenAPI(Swagger)规范。
3.1.json.dump():用于将 Python 对象(如字典、列表)转换为 JSON 格式的字符串,并将其写入文件或类文件对象(如网络连接、内存缓冲区等)。这一过程也被称为序列化(Serialization)。
json.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
参数说明:
obj
:需要序列化的 Python 对象(如字典、列表)。fp
:文件对象(如open('data.json', 'w')
返回的对象),用于写入 JSON 数据。indent
:可选参数,指定缩进空格数,用于美化 JSON 格式(如indent=2
)。ensure_ascii
:是否确保所有非 ASCII 字符被转义(默认True
,中文会被转义为\uXXXX
)。sort_keys
:是否按字典键排序(默认False
)。
import json data = { "name": "张三", "age": 30, "hobbies": ["阅读", "编程"], "address": {"city": "北京", "zipcode": "100000"} } # 将数据写入JSON文件 with open("data.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)
若需将 JSON 数据打印到控制台或日志中,可结合sys.stdout
使用:
import json import sys data = {"code": 200, "message": "操作成功", "data": [1, 2, 3]} json.dump(data, sys.stdout, ensure_ascii=False, indent=2) # 输出结果: '''{ "code": 200, "message": "操作成功", "data": [ 1, 2, 3 ] }'''
3.2.json.dumps():用于将 Python 对象(如字典、列表)转换为 JSON 格式的字符串。这一过程也被称为序列化(Serialization)。与json.dump()
(写入文件)不同,json.dumps()
返回字符串,适用于网络传输、打印输出或内存处理。
json.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
import json data = { "name": "张三", "age": 30, "hobbies": ["阅读", "编程"], "is_student": False, "address": {"city": "北京", "zipcode": "100000"} } # 将Python对象转换为JSON字符串 json_str = json.dumps(data, ensure_ascii=False, indent=2) print(json_str)
输出结果
{ "name": "张三", "age": 30, "hobbies": ["阅读", "编程"], "is_student": false, "address": { "city": "北京", "zipcode": "100000" } }
在 API 接口或网络通信中,常将数据序列化为 JSON 字符串后发送:
import requests import json data = {"user_id": 123, "action": "login"} json_payload = json.dumps(data) try: response = requests.post( "https://api.example.com/user", # 替换为实际API地址 data=json_payload, headers={"Content-Type": "application/json"} ) response.raise_for_status() # 检查请求是否成功(状态码200) except requests.exceptions.ConnectionError as e: print(f"网络连接错误: {e}") print("请检查域名是否正确、网络是否正常连接。") except requests.exceptions.Timeout: print("请求超时,请稍后重试。") except requests.exceptions.HTTPError as e: print(f"HTTP错误: {e}") except requests.exceptions.RequestException as e: print(f"请求发生异常: {e}") else: print("请求成功!") print(response.json()) # 处理响应数据
函数名 | 功能描述 |
json.dumps() | 将 Python 对象序列化为字符串(返回值为字符串,适用于网络传输)。 |
json.dump() | 将 Python 对象序列化并写入文件(需传入文件对象,如open('data.json', 'w') )。 |
json.loads() | 将 JSON 字符串反序列化为 Python 对象(解析字符串)。 |
json.load() | 从文件中读取 JSON 数据并反序列化为 Python 对象。 |
- 文本提取:从网页、日志文件、配置文件等非结构化文本中提取特定信息
- 数据验证:检查输入数据是否符合特定格式(如邮箱、URL、电话号码)
- 文本替换:根据特定模式批量替换文本内容
- 字符串分割:根据特定分隔符将字符串分割成多个部分
- 日志分析:从系统日志中提取关键信息(如时间戳、错误代码)
- 普通字符:匹配自身,如
a
匹配字符 "a" - 元字符:具有特殊含义的字符,如
.
匹配任意字符 - 字符类:用
[]
表示,匹配方括号内的任意一个字符,如[abc]
匹配 "a"、"b" 或 "c" - 量词:控制匹配次数,如
*
表示 0 次或多次,+
表示 1 次或多次,?
表示 0 次或 1 次 - 边界符:如
^
表示字符串开始,$
表示字符串结束 - 分组:用
()
表示,可以捕获匹配的内容,如(abc)
匹配并捕获 "abc"
①.基本元字符
元字符 | 描述 |
. |
匹配除换行符外的任意单个字符 |
^ |
匹配字符串的开始位置 |
$ |
匹配字符串的结束位置 |
* |
匹配前面的子表达式零次或多次 |
+ |
匹配前面的子表达式一次或多次 |
? |
匹配前面的子表达式零次或一次 |
{n} |
匹配前面的子表达式恰好 n 次 |
{n,} |
匹配前面的子表达式至少 n 次 |
{n,m} |
匹配前面的子表达式至少 n 次,最多 m 次 |
②.字符类元字符
[]
- 定义字符类,匹配方括号内的任意一个字符
- 例如:
[abc]
匹配 'a'、'b' 或 'c' - 可以使用连字符表示范围:
[a-z]
匹配任意小写字母 - 开头使用
^
表示取反:[^0-9]
匹配任意非数字字符
特殊字符 | 描述 |
\d |
匹配任意数字,等价于 [0-9] |
\D |
匹配任意非数字,等价于 [^0-9] |
\w |
匹配任意字母、数字或下划线,等价于 [a-zA-Z0-9_] |
\W |
匹配任意非字母、数字或下划线,等价于 [^a-zA-Z0-9_] |
\s |
匹配任意空白字符,包括空格、制表符、换页符等 |
\S |
匹配任意非空白字符 |
④.分组和引用
例如:(ab)+
匹配一个或多个连续的 "ab"
可以捕获匹配的内容,用于后续引用
例如:(\w+) \1
匹配重复的单词,如 "hello hello"
|
- 逻辑或,匹配两个或多个模式中的任意一个
例如:cat|dog
匹配 "cat" 或 "dog"
\
- 转义字符,用于取消元字符的特殊含义
例如:\.
匹配字面意义的点号,也用于表示特殊字符类,如 \d
、\s
等
例如:\w+(?=\d)
匹配后面跟数字的单词
例如:\w+(?!\d)
匹配后面不跟数字的单词
例如:(?<=\$)\d+
匹配前面是美元符号的数字
例如:(?<!\$)\d+
匹配前面不是美元符号的数字
✍\b
- 匹配单词边界,如 \bcat\b
只匹配独立的 "cat" 单词
✍\B
- 匹配非单词边界
✍(?:...)
- 非捕获组,分组但不捕获匹配的内容
⑨.贪婪匹配和惰性匹配
贪婪匹配:.* 意思是尽可能多的去匹配结果
惰性匹配:.*? 意思是尽可能少的去匹配结果
⑩.常用的正则表达式格式
re.findall(pattern, string, flags=0)
re.findall()的返回结果取决于正则表达式中是否包含捕获组:
- 无捕获组:直接返回所有匹配的子字符串列表
- 有一个捕获组:返回每个匹配中捕获组的内容列表
- 有多个捕获组:返回元组列表,每个元组包含一个匹配中所有捕获组的内容
import re
# 示例1:无捕获组,匹配所有数字
text1 = "hello 123 world 456"
result1 = re.findall(r'\d+', text1)
print(f"示例1结果: {result1}") # 输出: ['123', '456']
# 示例2:有一个捕获组,提取所有括号内的内容
text2 = "apple (red), banana (yellow), cherry (red)"
result2 = re.findall(r'\((.*?)\)', text2)
print(f"示例2结果: {result2}") # 输出: ['red', 'yellow', 'red']
# 示例3:有多个捕获组,提取所有水果和颜色
text3 = "apple=red, banana=yellow, cherry=red"
result3 = re.findall(r'(\w+)=(\w+)', text3)
print(f"示例3结果: {result3}") # 输出: [('apple', 'red'), ('banana', 'yellow'), ('cherry', 'red')]
# 示例4:使用标志参数忽略大小写
text4 = "Hello World, HELLO PYTHON"
result4 = re.findall(r'hello', text4, re.IGNORECASE)
print(f"示例4结果: {result4}") # 输出: ['Hello', 'HELLO']
# 示例5:多行匹配模式
text5 = "Line 1\nLine 2\nLine 3"
result5 = re.findall(r'^Line \d', text5, re.MULTILINE)
print(f"示例5结果: {result5}") # 输出: ['Line 1', 'Line 2', 'Line 3']
正则表达式中的 *
和 +
是贪婪的,会尽可能多地匹配字符
在量词后加 ?
可以使其变为非贪婪模式,尽可能少地匹配字符
使用 ^
和 $
匹配字符串的开始和结束
使用 \b
匹配单词边界,避免部分匹配
正则表达式中的特殊字符(如 .
, *
, +
等)需要使用 \
进行转义
可以使用原始字符串(在字符串前加 r
)避免 Python 字符串转义的干扰
不需要提取内容时,可以使用非捕获组 (?:...)
提高效率
多个捕获组的结果按元组形式返回,注意顺序和数量
4.2.re.sub("正则表达式","替换的字符串","原字符串","可选参数"):用于在字符串中替换所有匹配正则表达式的子串。这是处理文本数据时非常常用的功能,比如清洗数据、格式化文本等。
re.sub(pattern, repl, string, count=0, flags=0)
当 repl
是字符串时,可以使用以下特殊语法:
\g<group>
:引用指定编号或名称的捕获组\1
,\2
, ...:引用第 1、2 等捕获组\g<0>
:引用整个匹配的子串
import re
# 示例1:简单替换,将所有数字替换为X
text1 = "hello 123 world 456"
result1 = re.sub(r'\d', 'X', text1)
print(f"示例1结果: {result1}") # 输出: hello XXX world XXX
# 示例2:使用捕获组,交换姓名顺序
text2 = "Zhang San, Li Si, Wang Wu"
result2 = re.sub(r'(\w+) (\w+)', r'\2 \1', text2)
print(f"示例2结果: {result2}") # 输出: San Zhang, Si Li, Wu Wang
# 示例3:使用命名捕获组,格式化日期
text3 = "2023-10-01"
result3 = re.sub(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
r'\g<month>/\g<day>/\g<year>', text3)
print(f"示例3结果: {result3}") # 输出: 10/01/2023
# 示例4:使用函数进行动态替换,将数字乘以2
def double_number(match):
return str(int(match.group(0)) * 2)
text4 = "A1B2C3"
result4 = re.sub(r'\d', double_number, text4)
print(f"示例4结果: {result4}") # 输出: A2B4C6
# 示例5:限制替换次数
text5 = "apple apple apple"
result5 = re.sub(r'apple', 'orange', text5, count=2)
print(f"示例5结果: {result5}") # 输出: orange orange apple
# 示例6:忽略大小写替换
text6 = "Hello World, hello Python"
result6 = re.sub(r'hello', 'hi', text6, flags=re.IGNORECASE)
print(f"示例6结果: {result6}") # 输出: hi World, hi Python
def square_number(match):
num = int(match.group(0))
return str(num ** 2)
text = "a1b2c3"
result = re.sub(r'\d', square_number, text)
print(result) # 输出: a1b4c9
- 数据清洗:移除或替换特殊字符、多余空格等
- 文本格式化:将日期、电话号码等转换为统一格式
- 敏感信息处理:替换或掩码化敏感数据(如身份证号、信用卡号)
- 语法转换:在不同编程语言或格式之间转换语法
- 模板替换:根据规则动态生成文本内容
正则表达式中的特殊字符需要使用 \
进行转义
可以使用原始字符串(如 r'\d+'
)避免 Python 字符串转义的干扰
使用 ()
定义捕获组,可以在替换字符串中引用
使用非捕获组 (?:...)
避免不必要的捕获,提高效率
替换字符串中的反斜杠需要转义(如 \\
表示一个反斜杠)
- 可以使用原始字符串简化处理(如
r'\\section'
)
re.finditer(pattern, string, flags=0)
result = re.finditer(r"\d+", "我今年42岁,我有2000000元")
for item in result:
print(item.group(0))
#输出结果:42 2000000
import re
# 使用 re.finditer() 查找所有数字
result = re.finditer(r"\d+", "我今年42岁,我有2000000元")
# 遍历迭代器并打印匹配结果
for match in result:
print(f"匹配到的数字: {match.group(0)}")
print(f"位置: {match.start()} - {match.end()}")
print(f"原始字符串中的片段: {match.string[match.start():match.end()]}")
print("-" * 20)
"""输出结果:
匹配到的数字: 42
位置: 3 - 5
原始字符串中的片段: 42
--------------------
匹配到的数字: 2000000
位置: 8 - 15
原始字符串中的片段: 2000000
--------------------"""
group(0)
:返回整个匹配的字符串group(1), group(2), ...
:返回第 1、2 等捕获组的内容start()
:返回匹配开始的位置end()
:返回匹配结束的位置span()
:返回一个元组(start, end)
import re # 示例1:查找第一个数字 text1 = "hello 123 world 456" match1 = re.search(r'\d+', text1) if match1: print(f"找到数字: {match1.group(0)}") # 输出: 123 print(f"位置: {match1.start()} - {match1.end()}") # 输出: 6 - 9 else: print("未找到匹配项") # 示例2:使用捕获组提取信息 text2 = "我的邮箱是 example@domain.com,请查收" match2 = re.search(r'(\w+)@(\w+\.\w+)', text2) if match2: print(f"完整邮箱: {match2.group(0)}") # 输出: example@domain.com print(f"用户名: {match2.group(1)}") # 输出: example print(f"域名: {match2.group(2)}") # 输出: domain.com # 示例3:忽略大小写查找 text3 = "Hello World" match3 = re.search(r'world', text3, re.IGNORECASE) if match3: print(f"找到匹配: {match3.group(0)}") # 输出: World # 示例4:多行模式下的搜索 text4 = "第一行\n第二行\n第三行" match4 = re.search(r'^第(.*)行$', text4, re.MULTILINE) if match4: print(f"匹配行内容: {match4.group(0)}") # 输出: 第一行 print(f"捕获内容: {match4.group(1)}") # 输出: 一
import re # 通过用户输入获取密码 password = input("请输入密码:") if re.search(r'^[a-zA-Z0-9]+$', password): print("密码格式有效") else: print("密码格式无效,只能包含字母和数字")
- 提取特定信息:
import re # 定义要搜索的文本 text = "这是一个测试,商品价格:99.99元" match = re.search(r'价格:(\d+\.\d+)元', text) if match: price = float(match.group(1)) print(f"商品价格: {price} 元") else: print("未找到价格信息") #输出结果:商品价格: 99.99 元 #从用户输入获取 text import re # 从用户输入获取文本 text = input("请输入包含价格的文本(例如:商品价格:99.99元):") # 修改正则表达式,允许匹配整数或小数价格 match = re.search(r'价格:(\d+(?:\.\d+)?)元', text) if match: try: price = float(match.group(1)) print(f"成功提取价格: {price} 元") except ValueError: print("错误:无法将提取的内容转换为价格") else: print("未找到符合格式的价格信息") print("提示:请确保输入的文本包含如'价格:99.99元'或'价格:100元'的格式") import re # 从文件读取文本 try: with open('input.txt', 'r', encoding='utf-8') as file: text = file.read() except FileNotFoundError: print("文件未找到,请检查文件路径") exit() match = re.search(r'价格:(\d+\.\d+)元', text) if match: price = float(match.group(1)) print(f"商品价格: {price} 元") else: print("未找到价格信息")
- 验证输入格式:
import re # 通过用户输入获取电话号码 phone_number = input("请输入电话号码:") if re.search(r'^\d{11}$', phone_number): print("手机号码格式正确") else: print("手机号码格式错误,必须是11位数字"
4.5.re.match("正则表达式","原字符串","可选参数"):主要用于在字符串的起始位置对正则表达式进行匹配。要是匹配成功,它会返回一个匹配对象;要是匹配失败,则返回None
。
re.match(pattern, string, flags=0)
group([group1, ...])
:用于获取一个或多个分组的匹配内容。若不指定参数,则返回整个匹配结果。groups()
:返回一个包含所有分组匹配结果的元组。start([group])
:返回指定分组匹配的起始位置。若未指定分组,则返回整个匹配的起始位置。end([group])
:返回指定分组匹配的结束位置(不包含该位置的字符)。span([group])
:返回一个元组,包含指定分组匹配的起始和结束位置。
import re string = "Hello, world!" match = re.match("world", string) # 匹配失败,返回None search = re.search("world", string) # 匹配成功,返回匹配对象 print(match) # 输出:None print(search.group()) # 输出:world
4.6.re.compile("正则表达式","可选参数"):用于将正则表达式模式编译为一个正则表达式对象。编译后的对象可以多次使用,叫做预加载,提高匹配效率,尤其在需要重复匹配相同模式的场景下性能更优。
re.compile(pattern, flags=0)
import re # 编译正则表达式 pattern = re.compile(r'\d+') # 匹配一个或多个数字 # 使用编译后的对象进行匹配 text = "Hello 123 World 456" # 查找所有匹配 matches = pattern.findall(text) print(f"所有匹配: {matches}") # 输出: ['123', '456'] # 使用 search 方法查找第一个匹配 match = pattern.search(text) if match: print(f"第一个匹配: {match.group(0)}, 位置: {match.start()}") # 输出: 第一个匹配: 123, 位置: 6
import re text = "Hello 123 World 456" matches = re.findall(r'\d+', text) # 每次调用都编译一次正则表达式 print(matches)#输出:['123','456']
import re pattern = re.compile(r'\d+') # 编译一次 matches1 = pattern.findall("Hello 123") # 直接使用已编译的对象 matches2 = pattern.findall("World 456") # 再次使用,无需重新编译 print(matches1) # 输出: ['123'] print(matches2) # 输出: ['456']
pattern.match(string[, pos[, endpos]])
:从字符串的起始位置开始匹配。pattern.search(string[, pos[, endpos]])
:在字符串中搜索第一个匹配项。pattern.findall(string[, pos[, endpos]])
:返回所有匹配的字符串列表。pattern.finditer(string[, pos[, endpos]])
:返回匹配对象的迭代器。pattern.sub(repl, string[, count])
:替换匹配的子串。
import re # 预编译正则表达式 email_pattern = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b') # 待验证的邮箱列表 emails = ["test@example.com", "invalid.email", "user.name+tag@domain.co.uk"] # 循环验证邮箱 for email in emails: if email_pattern.match(email): print(f"{email} 是有效的邮箱地址") else: print(f"{email} 不是有效的邮箱地址")
re.IGNORECASE
或re.I
:忽略大小写。re.MULTILINE
或re.M
:多行匹配模式。re.DOTALL
或re.S
:使.
匹配包括换行符在内的所有字符。
import re # 添加模块导入语句 pattern = re.compile(r'hello', re.IGNORECASE) # 忽略大小写的匹配 print(pattern.search("Hello World").group(0)) # 输出: Hello
总之,re.compile
是优化正则表达式性能的重要工具,尤其适用于需要重复使用同一模式的场景。
4.7.re.S: 匹配模式修饰符,也可以写作 re.DOTALL
。
import re text = "hello\nworld" # 不使用 re.S,无法匹配换行符 print(re.findall("hello.world", text)) # 输出: [] # 使用 re.S,点号可以匹配换行符 print(re.findall("hello.world", text, re.S)) # 输出: ['hello\nworld']
这个修饰符在需要匹配多行文本时非常有用,比如处理包含换行的字符串或多行文本内容。re.DOTALL
是更具描述性的别名,功能与 re.S
完全相同。
5.CSV文件写入(内置模块)
import csv # 数据 data = [ ["姓名", "年龄", "城市"], ["张三", 25, "北京"], ["李四", 30, "上海"], ["王五", 28, "广州"] ] # 写入CSV文件 with open("people.csv", "w", newline="", encoding="utf-8") as file: writer = csv.writer(file) # 写入多行 writer.writerows(data) # 也可以单行写入 # writer.writerow(["赵六", 35, "深圳"])
import csv # 数据 data = [ {"姓名": "张三", "年龄": 25, "城市": "北京"}, {"姓名": "李四", "年龄": 30, "城市": "上海"}, {"姓名": "王五", "年龄": 28, "城市": "广州"} ] # 字段名(表头) fieldnames = ["姓名", "年龄", "城市"] with open("people_dict.csv", "w", newline="", encoding="utf-8") as file: writer = csv.DictWriter(file, fieldnames=fieldnames) # 写入表头 writer.writeheader() # 写入数据 writer.writerows(data) # 单行写入 # writer.writerow({"姓名": "赵六", "年龄": 35, "城市": "深圳"})
import csv with open("custom_sep.csv", "w", newline="", encoding="utf-8") as file: # 使用制表符作为分隔符(类似TSV文件) writer = csv.writer(file, delimiter="\t") writer.writerows([["a", "b", "c"], ["1", "2", "3"]])
注意事项:
5.4.enumerate(迭代对象,[0]:):枚举对象转换:用于将一个可迭代对象(如列表、元组、字符串等)转换为一个枚举对象,在遍历过程中同时返回「元素的索引」和「元素的值」。
enumerate(iterable, start=0)
当你需要遍历一个序列,同时又需要知道每个元素在序列中的位置(索引)时,enumerate()
可以简化代码。
例如,不使用 enumerate()
时,可能需要这样写:
fruits = ['apple', 'banana', 'cherry'] index = 0 for fruit in fruits: print(f"Index: {index}, Fruit: {fruit}") index += 1 # 手动维护索引
而使用 enumerate()
后,代码更简洁:
fruits = ['apple', 'banana', 'cherry'] for index, fruit in enumerate(fruits): print(f"Index: {index}, Fruit: {fruit}") ''' 输出结果: Index: 1, Fruit: apple Index: 2, Fruit: banana Index: 3, Fruit: cherry '''
如果从第二个数据(banana)开始迭代,可以先对列表进行切片处理,从索引 1 开始(因为 Python 列表索引从 0 开始,第二个元素的索引是 1),再传入enumerate()
函数中。
fruits = ['apple', 'banana', 'cherry'] # 从索引1开始切片,只处理'banana'和'cherry' for index, fruit in enumerate(fruits[1:]): # 注意:此时的index是相对于切片后列表的索引(从0开始) # 如果需要显示原列表中的真实索引,需要加上偏移量1 print(f"原索引: {index + 1}, Fruit: {fruit}") ''' 输出结果: 原索引: 1, Fruit: banana 原索引: 2, Fruit: cherry '''
简单说,enumerate()
的核心作用就是在遍历过程中同时获取元素及其位置信息,让代码更简洁高效。
自动爬虫
1.selemium:是一套用于Web应用程序自动化测试的工具集,同时也广泛用于网络数据爬取(合规场景下)、自动化操作浏览器(如自动填写表单、模拟用户交互等)。它的核心能力是直接控制浏览器(如 Chrome、Firefox),模拟真实用户的操作行为(点击、输入、跳转等),是 Web 自动化领域最流行的工具之一。
- 开发者编写代码:通过 Python/Java 等语言调用 Selenium WebDriver 的 API(如
click()
、send_keys()
)。 - WebDriver 转发指令:WebDriver 将代码中的操作指令(如 “点击按钮”)转换为浏览器能识别的协议(如 W3C WebDriver 协议)。
- 浏览器驱动执行:需提前安装对应浏览器的 “驱动程序”(如 Chrome 对应
chromedriver
),驱动接收指令后,控制浏览器执行具体操作。 - 浏览器返回结果:操作结果(如页面渲染、元素状态变化)通过驱动反馈给 WebDriver,最终可在代码中获取(如获取页面源码、元素文本)。