Python 爬虫和正则表达式是自动化网络数据提取中常用的两种技术。本文将详细介绍如何使用 Python 编写爬虫,结合正则表达式提取网页中的数据。
一、基础知识点
1. 安装必要库
爬虫通常需要使用库来处理 HTTP 请求和解析网页,常用库有:
requests
:用于发送 HTTP 请求,获取网页内容。re
:Python 自带的正则表达式库,用于模式匹配和提取数据。BeautifulSoup
(可选):如果你需要更高级的网页解析,可以使用它。
pip install requests beautifulsoup4
2. 使用 requests
获取网页内容
你可以使用 requests
库获取网页的 HTML 内容。
import requests
url = "https://example.com"
response = requests.get(url)
# 获取网页内容(HTML 文本)
html_content = response.text
print(html_content) # 输出网页的 HTML 源码
3. 正则表达式基础
正则表达式是一种模式匹配技术,用于从文本中提取特定格式的数据。Python 使用 re
模块来处理正则表达式。
3.1 常用正则表达式符号
.
:匹配除换行符外的任意字符。^
:匹配字符串的开头。$
:匹配字符串的结尾。*
:匹配 0 次或多次前面的字符。+
:匹配 1 次或多次前面的字符。?
:匹配 0 次或 1 次前面的字符。{n}
:匹配 n 次前面的字符。[]
:匹配括号中的任意字符,如[abc]
匹配a
、b
或c
。\d
:匹配任意数字,相当于[0-9]
。\w
:匹配任意字母、数字或下划线,相当于[a-zA-Z0-9_]
。\s
:匹配空白字符(如空格、制表符)。()
:用来分组和提取匹配的子字符串。
3.2 基本示例
import re
text = "My phone number is 123-456-7890."
# 匹配电话号码
pattern = r"\d{3}-\d{3}-\d{4}"
match = re.search(pattern, text)
if match:
print(f"匹配到的电话号码: {match.group()}")
在这个示例中,正则表达式 \d{3}-\d{3}-\d{4}
用来匹配类似于 123-456-7890
格式的电话号码。
4. 实战:使用 Python 爬虫和正则表达式抓取数据
下面我们将展示一个完整的实例,结合 Python 爬虫和正则表达式从网页上提取电子邮件地址。
4.1 获取网页 HTML
import requests
url = "https://example.com"
response = requests.get(url)
html_content = response.text
4.2 使用正则表达式提取邮箱地址
import re
# 定义正则表达式匹配邮箱地址
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
# 在网页内容中查找所有匹配的邮箱地址
emails = re.findall(email_pattern, html_content)
# 输出找到的邮箱
for email in emails:
print(email)
4.3 从多个网页抓取数据(爬取多个页面)
如果你想从多个网页抓取数据,可以使用循环或递归,常见的做法是处理分页,或递归遍历多个 URL。
import requests
import re
# 爬取多个页面
for page_num in range(1, 6):
url = f"https://example.com/page/{page_num}"
response = requests.get(url)
# 获取 HTML 内容
html_content = response.text
# 正则表达式提取信息
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
emails = re.findall(email_pattern, html_content)
# 打印提取的邮箱地址
for email in emails:
print(f"Page {page_num}: {email}")
5. 正则表达式高级技巧
5.1 捕获组和非捕获组
- 捕获组
()
:用于匹配和提取子字符串。例如(\d{3})-(\d{3})-(\d{4})
可以分别提取电话号码的各个部分。 - 非捕获组
(?:)
:匹配但不提取。例如(?:https?)://
匹配http
或https
开头的 URL,但不捕获http
或https
。
text = "My phone number is 123-456-7890."
pattern = r"(\d{3})-(\d{3})-(\d{4})"
match = re.search(pattern, text)
if match:
area_code = match.group(1) # 提取区号
print(f"区号: {area_code}")
5.2 贪婪与非贪婪匹配
- 默认情况下,
*
、+
是贪婪匹配,尽可能多地匹配字符。 - 非贪婪匹配在贪婪符号后加
?
。例如,.*?
是非贪婪匹配,它会尽可能少地匹配字符。
text = "<div>First</div><div>Second</div>"
# 贪婪匹配,会匹配到整个字符串
greedy_match = re.search(r"<div>.*</div>", text)
print(greedy_match.group()) # 输出 "<div>First</div><div>Second</div>"
# 非贪婪匹配,只匹配到第一个 div
non_greedy_match = re.search(r"<div>.*?</div>", text)
print(non_greedy_match.group()) # 输出 "<div>First</div>"
6. 使用 BeautifulSoup
与正则表达式结合
BeautifulSoup
是一个功能强大的 HTML 解析库,适合处理复杂的 HTML 页面。你可以结合它和正则表达式更精确地提取网页数据。
from bs4 import BeautifulSoup
import requests
import re
url = "https://example.com"
response = requests.get(url)
# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(response.text, "html.parser")
# 提取所有链接
for a_tag in soup.find_all("a", href=True):
href = a_tag['href']
if re.match(r"https?://", href): # 正则表达式匹配完整的 URL
print(href)
7. 处理反爬机制
许多网站有反爬机制,如检测爬虫的 IP 或使用 JavaScript 动态加载内容。如果遇到这些问题,可以考虑以下技术:
- 请求头模拟:模拟真实用户的请求头,避免被网站检测为爬虫。
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
response = requests.get(url, headers=headers)
- 使用代理:避免 IP 被封,可以使用代理池。
- 处理动态内容:如果网页使用 JavaScript 加载数据,考虑使用
Selenium
这样的浏览器自动化工具或直接抓取网站 API 数据。
8. re
模块的主要函数
re.search()
:在字符串中搜索符合正则表达式的模式,返回第一个匹配。re.findall()
:返回所有匹配的子串。re.sub()
:使用另一个字符串替换所有匹配的子串。re.match()
:只在字符串的开头进行匹配。
# 替换字符串中的所有数字
text = "Price: $45, Discount: 20%"
clean_text = re.sub(r'\d+', '[NUMBER]', text)
print(clean_text) # 输出 "Price: $[NUMBER], Discount: [NUMBER]%"
二、综合应用示例:爬取并解析数据
我们使用爬虫抓取一个网页,并用正则表达式提取其中的邮箱地址和价格。
import requests
import re
from bs4 import BeautifulSoup
# 1. 下载网页
url = "https://example.com"
response = requests.get(url)
html = response.text
# 2. 使用 BeautifulSoup 解析网页
soup = BeautifulSoup(html, 'html.parser')
# 3. 从网页中提取文本信息
text = soup.get_text()
# 4. 正则表达式提取邮箱和价格
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
price_pattern = r'\$\d+(\.\d{2})?'
emails = re.findall(email_pattern, text)
prices = re.findall(price_pattern, text)
# 5. 输出结果
print("Emails found:", emails)
print("Prices found:", prices)
优点:
- 灵活性强,爬虫和正则结合可以应对多种网页和数据提取需求。
- 正则表达式可以准确提取所需信息。
缺点:
- 如果网页结构变化,爬虫代码需要修改,尤其是基于正则提取的部分。
- 正则表达式对复杂的 HTML 结构不友好,不如
BeautifulSoup
处理嵌套标签那么直观。
三、动态网页处理
对于由 JavaScript 渲染的动态网页,使用 Selenium
等工具来加载网页并抓取数据。例如:
from selenium import webdriver
# 启动浏览器
driver = webdriver.Chrome()
driver.get("https://example.com")
# 等待页面加载
html = driver.page_source
# 使用 BeautifulSoup 解析动态内容
soup = BeautifulSoup(html, 'html.parser')
优点:
- 可以处理动态内容和 AJAX 加载的网页。
Selenium
支持模拟用户行为(点击、输入等)。
缺点:
- 速度较慢,因为需要模拟整个浏览器。
- 占用更多系统资源,不适合大规模数据抓取。
结论
使用 Python 进行爬虫和正则表达式处理是非常强大的组合。requests
和 BeautifulSoup
适合处理静态网页,而正则表达式可以快速提取文本中的特定模式。然而,处理复杂网页时,爬虫的维护成本较高,特别是正则表达式对于动态网页或嵌套结构的处理较为局限。