1. 为什么选择BeautifulSoup进行图片爬取?
在Python爬虫领域,BeautifulSoup(简称bs4)作为HTML/XML解析库已经存在了近20年。根据2023年的开发者调查,仍有67%的Python爬虫项目在使用bs4进行页面解析。我最初选择bs4而非正则表达式或XPath的原因很简单——它的API设计完全符合人类直觉。
当我们需要从网页中提取图片时,bs4的find_all('img')方法可以直接锁定所有图片标签。相比之下,XPath需要记忆复杂的路径表达式,而正则表达式面对嵌套HTML时容易失控。bs4的另一个优势是自动处理编码问题,这在处理中文网站时尤为重要。
实际案例:我曾用正则表达式爬取某摄影网站,结果因为
<img>标签换行导致匹配失败。改用bs4后,同样的问题再未出现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与反爬策略规避
2.1 基础工具链搭建
建议使用Python 3.8+环境,安装以下核心包:
bash复制pip install beautifulsoup4 requests fake-useragent
其中fake-useragent用于随机生成浏览器UA,这是突破基础反爬的第一道防线。我习惯在项目根目录创建utils.py存放工具函数:
python复制from fake_useragent import UserAgent
def get_random_headers():
return {
'User-Agent': UserAgent().random,
'Accept-Language': 'zh-CN,zh;q=0.9'
}
2.2 应对图片防盗链技术
现代网站常用的图片防盗链技术会检查HTTP Referer头。解决方法是在请求头中添加来源域名:
python复制headers = get_random_headers()
headers.update({'Referer': 'https://target-domain.com'})
但更稳妥的做法是直接使用requests的session对象保持会话状态:
python复制session = requests.Session()
session.headers.update(get_random_headers())
response = session.get(url)
3. 图片链接提取实战
3.1 基础图片抓取流程
标准的图片抓取流程包含三个关键步骤:
- HTML获取:使用requests获取页面源码
- 元素定位:用bs4解析并定位img标签
- 资源下载:提取src属性并保存文件
完整示例代码:
python复制from bs4 import BeautifulSoup
import requests
import os
def download_images(url, save_dir):
os.makedirs(save_dir, exist_ok=True)
html = requests.get(url, headers=get_random_headers()).text
soup = BeautifulSoup(html, 'html.parser')
for idx, img in enumerate(soup.find_all('img')):
img_url = img.get('src')
if not img_url.startswith('http'):
img_url = url + img_url if img_url.startswith('/') else url + '/' + img_url
try:
img_data = requests.get(img_url, stream=True).content
with open(f'{save_dir}/image_{idx}.jpg', 'wb') as f:
f.write(img_data)
except Exception as e:
print(f"下载失败 {img_url}: {str(e)}")
3.2 高级定位技巧
很多网站使用懒加载技术,真实图片URL可能藏在data-src属性中:
python复制img_url = img.get('data-src') or img.get('src')
对于背景图片(CSS background-image),需要用正则表达式提取:
python复制import re
style = img.get('style')
if style and 'background-image' in style:
bg_url = re.search(r'url\(["\']?(.*?)["\']?\)', style).group(1)
4. 实战中的七个关键陷阱
4.1 相对路径处理
当遇到src="images/photo.jpg"这类相对路径时,必须拼接基准URL:
python复制from urllib.parse import urljoin
img_url = urljoin(url, img.get('src'))
4.2 动态加载内容
对于JavaScript动态加载的内容,bs4无法直接获取。此时需要:
- 使用浏览器开发者工具分析XHR请求
- 直接调用网站API接口
- 或者使用selenium模拟浏览器
4.3 大文件下载优化
下载高清图片时应该使用流式下载,避免内存溢出:
python复制response = requests.get(img_url, stream=True)
with open(filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
4.4 文件名冲突处理
建议使用MD5哈希生成唯一文件名:
python复制import hashlib
file_hash = hashlib.md5(img_url.encode()).hexdigest()
filename = f"{save_dir}/{file_hash}.jpg"
4.5 代理IP轮换
当遇到IP封锁时,需要配置代理池:
python复制proxies = {
'http': 'http://proxy_ip:port',
'https': 'http://proxy_ip:port'
}
response = requests.get(url, proxies=proxies)
4.6 异常处理机制
完善的异常处理应该包含:
python复制try:
response = requests.get(url, timeout=10)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"请求失败: {str(e)}")
return None
4.7 法律风险规避
务必遵守robots.txt规则,在代码中添加:
python复制from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url(urljoin(url, '/robots.txt'))
rp.read()
if not rp.can_fetch('*', url):
print("此页面禁止爬取")
return
5. 性能优化方案
5.1 多线程下载
使用concurrent.futures实现并行下载:
python复制from concurrent.futures import ThreadPoolExecutor
def download_single(img_url):
# 单图片下载逻辑
with ThreadPoolExecutor(max_workers=8) as executor:
executor.map(download_single, img_urls)
5.2 缓存机制
使用磁盘缓存避免重复下载:
python复制from os.path import exists
if not exists(filename):
# 执行下载
else:
print(f"{filename} 已存在")
5.3 增量爬取策略
记录已爬取的URL:
python复制import sqlite3
conn = sqlite3.connect('crawler.db')
cursor = conn.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS crawled_urls (url TEXT PRIMARY KEY)')
def is_crawled(url):
return cursor.execute('SELECT 1 FROM crawled_urls WHERE url=?', (url,)).fetchone()
6. 项目结构建议
规范的爬虫项目应该包含以下目录:
code复制/project
/images # 图片存储
/utils # 工具函数
headers.py # UA生成
proxy.py # 代理管理
/spiders # 爬虫核心
base.py # 基础爬虫类
image.py # 图片爬虫
config.py # 配置文件
main.py # 入口文件
在base.py中定义基础爬虫类:
python复制class BaseSpider:
def __init__(self):
self.session = requests.Session()
self.session.headers.update(get_random_headers())
def parse(self, html):
raise NotImplementedError
def save(self, item):
# 通用存储逻辑
7. 真实案例:摄影网站爬虫
以某摄影社区为例,其图片藏在复杂的JavaScript结构中。解决方案是:
- 首先获取包含图片数据的JSON
- 解析出高清图URL
- 批量下载
关键代码片段:
python复制import json
# 从页面中提取JSON数据
script_data = soup.find('script', {'type': 'application/ld+json'}).string
image_info = json.loads(script_data)['image']
# 下载原图
original_url = image_info['contentUrl']
response = session.get(original_url, stream=True)
这个案例教会我:现代网站的数据往往通过API接口获取,直接分析XHR请求比解析HTML更高效。
