Python 爬虫知识

本文系统梳理了 Python 爬虫的基础知识,从最基础的 urllib 和正则表达式,到解析库 BeautifulSoup,再到更加人性化的 Requests 库,以及进阶的多进程/异步并发抓取,最后介绍了应对动态网页的 Selenium。非常适合想要入门爬虫的新手学习。

1. 使用 re 和 urllib 进行基础爬取

在 Python 中,最基础的网络请求库是内置的 urllib。配合正则表达式 (re 模块),我们可以实现最原始的网页抓取和信息提取。

1.1 发送请求与读取响应

使用 urllib.request.urlopen() 可以向指定的 URL 发送请求,并读取响应的 HTML 源码。读取出来的通常是字节 (bytes),我们需要使用 decode('utf-8') 将其转换为字符串。

1
2
3
4
5
6
7
8
import urllib.request

url = 'http://www.example.com'
# 发起请求并读取网页内容
response = urllib.request.urlopen(url)
html_content = response.read().decode('utf-8')

print(html_content)

1.2 结合正则表达式提取数据

一旦我们获取了 HTML 源码,就可以使用 re 模块来匹配和提取我们需要的数据,例如所有的图片链接或特定格式的文本。

1
2
3
4
5
6
7
8
import re

# 假设我们在网页源码中寻找所有的图片链接 (形如 src="http://...")
img_pattern = re.compile(r'src="(http://.*?\.jpg)"')
image_urls = re.findall(img_pattern, html_content)

for img in image_urls:
print("找到图片链接:", img)

提示:虽然 urllib + re 是爬虫的基础,但使用正则表达式来解析复杂的 HTML 极其容易出错且难以维护。在实际开发中,我们更多使用下文介绍的专门解析库。


2. 使用 BeautifulSoup 解析网页

BeautifulSoup 是 Python 中最常用的 HTML/XML 解析库之一。它能把复杂的 HTML 文档转换成一个复杂的树形结构,每个节点都是 Python 对象,方便我们提取数据。

2.1 安装与基本使用

安装:pip install beautifulsoup4 lxml (推荐使用 lxml 作为解析器,速度更快)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
</body>
</html>
"""

# 使用 lxml 解析器构建 soup 对象
soup = BeautifulSoup(html_doc, 'lxml')

# 获取标题标签
print(soup.title) # <title>The Dormouse's story</title>
print(soup.title.string) # The Dormouse's story

2.2 查找元素

BeautifulSoup 提供了两种最常用的查找方法:find()find_all()

  • find():返回匹配的第一个元素。
  • find_all():返回一个包含所有匹配元素的列表。
1
2
3
4
5
6
7
8
9
# 找到所有 <a> 标签
all_links = soup.find_all('a')
for link in all_links:
# 获取标签的属性和文本内容
print(link.get('href'), link.text)

# 根据 class 和 id 查找特定元素
specific_link = soup.find('a', id='link1')
class_links = soup.find_all('a', class_='sister') # 注意是 class_

3. 使用 Requests 优雅地发送请求

相比于内置的 urllib,第三方库 requests 的 API 设计更加人性化,被称为“HTTP for Humans”。

3.1 安装与基本请求

安装:pip install requests

1
2
3
4
5
6
7
import requests

url = 'http://httpbin.org/get'
response = requests.get(url)

print(response.status_code) # 状态码 200
print(response.text) # 响应内容的字符串形式

3.2 携带参数与请求头 (Headers)

许多网站会根据请求头(尤其是 User-Agent)来判断请求是否来自于真实的浏览器。如果不带 User-Agent,很容易被网站的反爬虫机制拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
url = 'http://httpbin.org/get'

# GET 请求的查询参数
params = {
'name': 'weilai',
'age': 20
}

# 伪装成 Chrome 浏览器
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}

response = requests.get(url, params=params, headers=headers)
print(response.json()) # 如果返回的是 JSON 数据,可以直接解析为字典

3.3 处理 POST 请求

登录或者提交表单通常需要使用 POST 请求。

1
2
3
4
5
post_url = 'http://httpbin.org/post'
data = {'username': 'admin', 'password': '123'}

response = requests.post(post_url, data=data)
print(response.text)

4. 多进程、分布式与异步并发爬取

当需要抓取大量数据时,单线程的爬虫速度极慢。我们可以利用 Python 的并发特性来加速爬取。

4.1 多进程 (multiprocessing)

Python 的多线程受到 GIL(全局解释器锁)的限制,无法充分利用多核 CPU。对于爬虫这种 I/O 密集型 + 数据处理的任务,使用多进程池 (Pool) 是一个不错的加速方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
from multiprocessing import Pool

def fetch_url(url):
try:
response = requests.get(url, timeout=5)
print(f"成功抓取 {url}, 状态码: {response.status_code}")
except Exception as e:
print(f"抓取失败 {url}: {e}")

if __name__ == '__main__':
urls = [f'http://httpbin.org/get?page={i}' for i in range(10)]

# 创建一个包含 4 个进程的进程池
with Pool(4) as pool:
pool.map(fetch_url, urls)

4.2 异步 I/O (asyncio + aiohttp)

相比多进程,异步 I/O 更加轻量级,它在单线程下通过事件循环 (Event Loop) 在等待网络 I/O 时切换任务,能够支持极高的并发量。
通常我们需要配合 aiohttp 库来发起异步请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import asyncio
import aiohttp
import time

async def fetch(session, url):
async with session.get(url) as response:
return await response.text()

async def main():
urls = [f'http://httpbin.org/get?page={i}' for i in range(10)]
async with aiohttp.ClientSession() as session:
# 创建所有异步任务
tasks = [asyncio.create_task(fetch(session, url)) for url in urls]
# 并发执行并等待所有任务完成
results = await asyncio.gather(*tasks)
print(f"成功获取 {len(results)} 个页面的内容。")

if __name__ == '__main__':
start = time.time()
asyncio.run(main())
print(f"异步爬取耗时: {time.time() - start:.2f} 秒")

5. 应对动态网页:Selenium 的使用

传统的爬虫库(如 requests)只能获取服务器返回的初始 HTML 源码。如果网页的数据是通过 JavaScript 异步加载(Ajax)渲染出来的,requests 抓到的源码里就不会包含这些数据。

为了解决这个问题,我们可以使用 Selenium。它是一个自动化测试工具,可以驱动真实的浏览器(如 Chrome)打开网页,执行 JavaScript 代码,从而获取渲染后的最终页面。

5.1 安装与配置

  1. 安装库:pip install selenium
  2. 下载驱动:需要下载与你本地 Chrome 浏览器版本对应的 chromedriver,并将其加入系统环境变量。

5.2 基础用法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from selenium import webdriver
from selenium.webdriver.common.by import By
import time

# 启动 Chrome 浏览器 (如果配置了环境变量,可以直接调用)
driver = webdriver.Chrome()

try:
# 访问目标网页
driver.get("https://www.baidu.com")

# 找到搜索输入框并输入关键字
search_box = driver.find_element(By.ID, "kw")
search_box.send_keys("Python 爬虫")

# 找到搜索按钮并点击
submit_btn = driver.find_element(By.ID, "su")
submit_btn.click()

# 等待页面渲染加载
time.sleep(3)

# 获取渲染后的网页源码 (此时包含通过 JS 加载的数据)
page_source = driver.page_source
print("页面源码长度:", len(page_source))

finally:
# 无论如何,最后一定要关闭浏览器释放资源
driver.quit()

5.3 无头模式 (Headless)

在服务器或者后台运行爬虫时,我们不需要弹出可视化的浏览器窗口。可以开启 Chrome 的无头模式。

1
2
3
4
5
6
7
8
9
10
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument("--headless") # 无界面模式
chrome_options.add_argument("--disable-gpu")

driver = webdriver.Chrome(options=chrome_options)
driver.get("https://www.baidu.com")
print(driver.title)
driver.quit()

总结:爬虫技术从发送请求 (urllib / requests),到解析数据 (re / BeautifulSoup / XPath),再到突破并发瓶颈 (multiprocessing / asyncio) 和应对动态渲染 (Selenium),构成了一个完整的技能树。熟练掌握这些工具,你就能应对绝大多数的数据采集需求。