Python实战:百度图片爬虫开发全流程
网络爬虫(Web Crawler)是一种自动获取网页内容的程序,它通过模拟浏览器行为,向目标网站发送请求并解析返回的数据,从而提取所需信息。爬虫广泛应用于搜索引擎、数据挖掘、信息监控等领域。本章从“图片下载—处理—存储”三个关键环节入手,系统性地介绍了如何使用 Python 实现图片爬取的后续流程。通过requests库实现了高效的二进制图片下载,借助Pillow进行了图像的尺寸调整、格式转换与压
简介:在Python编程中,网络爬虫是获取网络数据的重要手段,尤其在图像数据采集方面具有广泛应用。本文详细讲解如何使用Python实现百度图片的自动爬取,涵盖网络爬虫基础原理、requests库发送HTTP请求、BeautifulSoup解析HTML内容、图片链接提取与下载、多线程加速以及图片处理技术(如PIL库)。通过本教程,读者可掌握完整的图片爬虫开发流程,并为后续数据挖掘、图像分析等任务打下坚实基础。 
1. 百度图片爬虫的核心概念与应用场景
1.1 网络爬虫的基本定义
网络爬虫(Web Crawler)是一种自动获取网页内容的程序,它通过模拟浏览器行为,向目标网站发送请求并解析返回的数据,从而提取所需信息。爬虫广泛应用于搜索引擎、数据挖掘、信息监控等领域。
1.2 图片爬虫的特殊性
与通用网页爬虫不同,图片爬虫专注于提取图像资源。其难点在于图片数据通常不直接嵌入页面,而是通过延迟加载(Lazy Load)或Ajax接口动态获取。此外,图片的存储格式、大小、分辨率等因素也增加了爬取与处理的复杂性。
1.3 百度图片搜索引擎的结构特点
百度图片搜索引擎通过关键词匹配返回相关图片结果。其搜索结果页通常包含缩略图链接,而高清图链接往往隐藏在JavaScript渲染后的DOM结构或独立接口中。因此,爬取百度图片需要深入分析URL构造规则与页面渲染机制。
1.4 百度图片爬取的合法合规原则
在进行图片爬取时,必须遵守《网络安全法》和网站的Robots协议,尊重数据版权与网站负载限制。建议设置合理的请求间隔,避免对目标服务器造成压力,同时明确数据用途,仅用于合法合规的场景。
1.5 实际应用场景举例
百度图片爬虫技术广泛应用于多个领域:
- 电商行业 :用于商品图像采集与竞品分析;
- 人工智能训练 :构建图像识别训练集;
- 内容创作辅助 :收集灵感图、素材图;
- 学术研究 :图像语义分析、图像风格迁移等任务的数据准备。
通过本章学习,读者将对图片爬虫技术建立整体认知,为后续实战打下坚实基础。
2. Python爬虫基础与requests库深度实践
在进入图片爬虫实战之前,必须对Python爬虫的基本构建模块进行系统性掌握。本章将从网络请求的基础理论入手,逐步深入到使用 requests 库进行高效网络数据抓取的实践环节。本章不仅会讲解HTTP协议的基本原理、状态码的含义、请求头的配置等基础知识,还会深入剖析 requests 库的核心功能,包括GET/POST请求的发送、参数传递、Session管理等。最后,我们还将构建一套完善的异常处理机制,确保爬虫程序具备良好的健壮性与可维护性。
2.1 网络请求的基本流程
在Python中实现网络爬虫,首先要理解网络请求的基本流程。这一流程涵盖了从客户端发起请求,到服务器响应并返回数据的全过程。理解这一过程对于构建高效、稳定的爬虫系统至关重要。
2.1.1 HTTP协议基础与请求/响应模型
HTTP(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,用于从Web服务器传输超文本到本地浏览器的传输协议。它是一种无状态、基于请求/响应模型的协议。
一个完整的HTTP请求通常包括以下组成部分:
- 请求行(Request Line) :包括请求方法(如GET、POST)、请求的URL和HTTP版本。
- 请求头(Headers) :包含客户端发送的元信息,如User-Agent、Accept、Content-Type等。
- 请求体(Body) :仅在POST、PUT等方法中出现,用于传递数据。
服务器接收到请求后,返回响应,响应结构如下:
- 状态行(Status Line) :包括HTTP版本、状态码和状态消息。
- 响应头(Response Headers) :提供服务器的信息,如Content-Type、Content-Length等。
- 响应体(Response Body) :实际返回的数据内容,如HTML文档、JSON数据等。
例如,一个简单的GET请求如下:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 ...
服务器响应示例:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
<html>...</html>
逻辑分析 :
- 请求行指明了请求方法为GET,请求路径为/index.html,使用HTTP 1.1协议。
- 请求头中的Host字段指明了目标主机,User-Agent用于标识客户端类型。
- 响应状态码200表示请求成功,Content-Type说明返回内容是HTML文档。
2.1.2 常见状态码与含义
HTTP状态码是服务器对客户端请求的响应状态标识,由三位数字组成。以下是常见的状态码及其含义:
| 状态码 | 含义描述 |
|---|---|
| 200 | 请求成功 |
| 301 | 永久重定向 |
| 302 | 临时重定向 |
| 400 | 客户端请求有误 |
| 403 | 禁止访问 |
| 404 | 资源未找到 |
| 500 | 服务器内部错误 |
这些状态码可以帮助我们判断请求是否成功,或者是否需要进行重试、重定向等处理。
逻辑分析 :
- 200系列表示成功;
- 300系列表示需要进一步操作(如重定向);
- 400系列表示客户端错误;
- 500系列表示服务器错误。
2.1.3 请求头与用户代理配置
请求头是HTTP请求的重要组成部分,常用于传递客户端的附加信息。其中, User-Agent 字段尤为重要,它标识了客户端浏览器的类型和操作系统,用于服务器进行适配。
在使用 requests 库时,我们可以自定义请求头,以模拟浏览器访问,避免被网站反爬机制识别为机器人。
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
response = requests.get('https://www.example.com', headers=headers)
print(response.status_code)
逐行分析 :
- 第1行导入requests库;
- 第3-7行定义自定义请求头,模拟Chrome浏览器;
- 第9行发送GET请求,并传入自定义headers;
- 第10行输出状态码,用于判断请求是否成功。参数说明 :
-User-Agent:用于伪装成浏览器;
-Accept-Language:告知服务器可接受的语言类型;
-Accept-Encoding:指定客户端接受的压缩格式;
-Connection:控制是否保持连接。
2.2 requests库的核心使用方法
requests 是Python中最常用的HTTP客户端库,它简化了HTTP请求的发送和响应处理过程。本节将深入讲解其核心功能,包括GET/POST请求的发送、参数传递方式、以及Session对象的使用。
2.2.1 发送GET与POST请求
GET请求通常用于获取数据,参数直接拼接在URL中;POST请求用于提交数据,参数通常放在请求体中。
发送GET请求示例 :
import requests
response = requests.get('https://httpbin.org/get', params={'key1': 'value1', 'key2': 'value2'})
print(response.url)
逻辑分析 :
- 使用params参数传递查询参数;
- 最终请求URL为:https://httpbin.org/get?key1=value1&key2=value2。
发送POST请求示例 :
response = requests.post('https://httpbin.org/post', data={'key1': 'value1'})
print(response.text)
逻辑分析 :
- 使用data参数提交表单数据;
- 数据将作为请求体发送给服务器;
-response.text返回服务器返回的文本内容。
2.2.2 参数传递与URL编码
在GET请求中,参数通常通过URL查询字符串传递,此时需要进行URL编码,确保特殊字符正确传输。
import requests
params = {'q': 'Python requests', 'page': 1}
response = requests.get('https://search.example.com', params=params)
print(response.url)
逻辑分析 :
-params字典将自动进行URL编码;
- 输出的URL为:https://search.example.com?q=Python+requests&page=1;
- 其中空格被编码为+,参数之间用&连接。
对于POST请求,若需传递JSON数据,可使用 json 参数:
response = requests.post('https://api.example.com/data', json={'name': 'John', 'age': 30})
参数说明 :
-json参数会自动设置Content-Type: application/json;
- 数据将被序列化为JSON格式发送。
2.2.3 Session对象与持久连接
当需要多次请求同一网站时,使用 Session 对象可以保持连接状态,提升效率。
import requests
session = requests.Session()
session.headers.update({'User-Agent': 'MyApp/1.0'})
response1 = session.get('https://httpbin.org/get')
response2 = session.post('https://httpbin.org/post', data={'key': 'value'})
print(response1.status_code, response2.status_code)
逻辑分析 :
- 创建一个Session对象;
- 设置默认请求头;
- 多次使用该Session对象发送请求,复用底层TCP连接;
- 提升请求效率,避免重复建立连接。参数说明 :
-Session.headers:设置默认请求头;
- 后续请求会自动继承该头信息。
2.3 爬虫异常处理机制
网络请求过程中,可能遇到各种异常,如连接超时、服务器错误、IP被封等。构建健壮的爬虫程序必须具备完善的异常处理机制。
2.3.1 请求超时设置与异常捕获
为了避免程序长时间卡住,应设置合理的超时时间。 requests 支持设置连接和读取超时。
import requests
try:
response = requests.get('https://httpbin.org/delay/5', timeout=3)
except requests.exceptions.Timeout:
print("请求超时,请检查网络或重试")
逻辑分析 :
- 设置timeout=3,表示3秒内未收到响应则抛出Timeout异常;
- 捕获异常后打印提示信息;
- 防止程序因网络延迟而卡死。参数说明 :
-timeout:可接受单个数值(同时限制连接和读取)或元组(连接时间, 读取时间)。
2.3.2 重试策略与连接池管理
在遇到临时性网络问题时,合理设置重试策略可以提高爬虫成功率。
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import requests
session = requests.Session()
retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries))
response = session.get('https://httpbin.org/status/500')
逻辑分析 :
- 使用Retry设置最大重试次数为5次;
- 遇到5xx错误自动重试;
-backoff_factor控制重试间隔时间;
-HTTPAdapter绑定到Session对象,统一管理连接池。参数说明 :
-total:最大重试次数;
-backoff_factor:重试间隔的退避因子;
-status_forcelist:触发重试的状态码列表。
2.3.3 反爬应对与请求频率控制
为了避免被网站封禁IP,应合理控制请求频率,模拟浏览器行为。
import requests
import time
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
for i in range(5):
response = requests.get('https://httpbin.org/get', headers=headers)
print(f"第{i+1}次请求,状态码:{response.status_code}")
time.sleep(2) # 每次请求间隔2秒
逻辑分析 :
- 循环发送5次GET请求;
- 每次请求间隔2秒,避免触发反爬机制;
- 模拟真实用户行为,降低被封IP风险。参数说明 :
-time.sleep(2):控制请求频率;
- 适当使用代理IP、更换User-Agent等策略可进一步增强反反爬能力。
本章通过由浅入深的方式,从HTTP协议基础讲起,逐步过渡到 requests 库的使用与异常处理机制的构建。掌握本章内容,将为后续构建百度图片爬虫打下坚实的基础。在下一章中,我们将深入探讨如何构造百度图片搜索URL,并解析返回的HTML结构,提取目标图片链接。
3. 百度图片搜索URL构造与HTML解析
在掌握了Python网络请求与爬虫基础后,本章将聚焦于百度图片搜索接口的分析与HTML文档的解析。这一阶段是构建百度图片爬虫项目的核心环节之一。通过深入解析URL构造逻辑、理解HTML结构,并掌握高效的解析方法,我们能够精准提取出所需的图片链接信息。
本章将从百度图片搜索接口的URL构造入手,逐步展开对HTML文档的结构解析,并最终实现图片链接的提取策略。本章内容将为后续的图片下载与存储打下坚实的技术基础。
3.1 百度图片搜索接口分析
要从百度图片中获取数据,首先需要了解其搜索接口的工作机制。通过构造特定格式的URL,可以模拟用户输入关键词进行搜索的行为,从而获取对应的图片结果页面。
3.1.1 搜索关键词参数构造
百度图片搜索的URL通常如下格式:
https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&sf=1&fmq=1719571200000&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=关键词
其中, word 参数表示搜索关键词。我们可以使用Python的 urllib.parse.quote 函数对关键词进行URL编码,以确保中文或特殊字符能被正确传递。
代码示例:构造搜索关键词URL
import urllib.parse
def build_search_url(keyword):
base_url = "https://image.baidu.com/search/index?"
params = {
'tn': 'baiduimage',
'ipn': 'r',
'ct': '201326592',
'cl': '2',
'lm': '-1',
'st': '-1',
'sf': '1',
'fmq': '1719571200000',
'pv': '',
'ic': '0',
'nc': '1',
'z': '',
'se': '1',
'showtab': '0',
'fb': '0',
'width': '',
'height': '',
'face': '0',
'istype': '2',
'ie': 'utf-8',
'word': keyword
}
encoded_params = urllib.parse.urlencode(params)
return base_url + encoded_params
# 示例:搜索“猫”
search_url = build_search_url("猫")
print(search_url)
逻辑分析:
urllib.parse.urlencode将字典参数转换为URL编码的查询字符串。base_url是百度图片搜索的固定前缀。params字典中包含多个请求参数,其中word是搜索关键词。- 构造完成的URL可以用于发起GET请求,获取百度图片搜索结果页面。
参数说明:
| 参数名 | 说明 |
|---|---|
tn |
百度图片的搜索标识 |
ipn |
请求来源标识 |
ct |
内容类型编码 |
cl |
颜色参数 |
word |
搜索关键词(必须) |
⚠️ 注意:部分参数在实际请求中可能不需要修改,但保留这些参数有助于更接近真实用户行为,避免被反爬虫机制拦截。
3.1.2 分页机制与URL拼接技巧
百度图片搜索结果支持分页加载,每页显示固定数量的图片。通过分析URL结构可以发现,分页主要通过 pn 参数控制,表示偏移量(即从第几条结果开始显示)。
例如:
pn=0 # 第一页
pn=30 # 第二页(假设每页30条)
pn=60 # 第三页
代码示例:实现分页功能
def build_paginated_url(keyword, page=1, per_page=30):
base_url = "https://image.baidu.com/search/index?"
offset = (page - 1) * per_page
params = {
'tn': 'baiduimage',
'ipn': 'r',
'ct': '201326592',
'cl': '2',
'lm': '-1',
'st': '-1',
'sf': '1',
'fmq': '1719571200000',
'pv': '',
'ic': '0',
'nc': '1',
'z': '',
'se': '1',
'showtab': '0',
'fb': '0',
'width': '',
'height': '',
'face': '0',
'istype': '2',
'ie': 'utf-8',
'word': keyword,
'pn': offset
}
encoded_params = urllib.parse.urlencode(params)
return base_url + encoded_params
# 示例:获取“猫”关键词的第2页结果
search_url = build_paginated_url("猫", page=2)
print(search_url)
逻辑分析:
page参数控制页码,per_page表示每页数量(默认30条)。offset = (page - 1) * per_page计算出起始偏移量。params['pn'] = offset设置分页参数。- 构造出的URL可以实现分页访问。
表格:分页参数对照表
| 页码 | pn值 | 每页数量 |
|---|---|---|
| 第1页 | 0 | 30 |
| 第2页 | 30 | 30 |
| 第3页 | 60 | 30 |
| 第4页 | 90 | 30 |
3.1.3 Ajax加载与数据接口识别
百度图片搜索结果采用Ajax动态加载机制,这意味着当用户滚动页面时,新的图片会通过Ajax请求从服务器加载。为了更高效地获取数据,我们可以直接访问Ajax请求的数据接口,而不是解析HTML页面。
通过浏览器开发者工具(F12)的Network面板,可以捕获到Ajax请求的URL,通常返回的是JSON格式的数据,包含图片的真实地址。
示例Ajax请求URL:
https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord=猫&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&word=猫&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&force=&pn=30&rn=30&gsm=1e&1719571200000=
特点:
- 返回JSON数据,便于解析。
- 参数结构与普通搜索页面类似。
pn控制偏移量,rn控制每页数量。
代码示例:获取Ajax接口数据
import requests
import json
def fetch_ajax_images(keyword, page=1, per_page=30):
base_url = "https://image.baidu.com/search/acjson?"
offset = (page - 1) * per_page
params = {
'tn': 'resultjson_com',
'ipn': 'rj',
'ct': '201326592',
'cl': '2',
'lm': '-1',
'ie': 'utf-8',
'oe': 'utf-8',
'adpicid': '',
'st': '-1',
'z': '',
'ic': '0',
'word': keyword,
's': '',
'se': '',
'tab': '',
'width': '',
'height': '',
'face': '0',
'istype': '2',
'qc': '',
'nc': '1',
'fr': '',
'expermode': '',
'force': '',
'pn': offset,
'rn': per_page,
'gsm': hex(offset)[2:], # 百度部分参数为16进制
'1719571200000': '' # 时间戳参数(可选)
}
headers = {
'User-Agent': 'Mozilla/5.0'
}
response = requests.get(base_url, params=params, headers=headers)
if response.status_code == 200:
return response.json()
else:
print(f"请求失败,状态码:{response.status_code}")
return None
# 示例:获取“猫”的第一页Ajax数据
data = fetch_ajax_images("猫", page=1)
print(json.dumps(data, indent=2))
逻辑分析:
- 使用
requests.get发送带有参数的GET请求。 - 设置
headers中的User-Agent模拟浏览器行为。 - 若请求成功,返回JSON格式的数据。
- 可以提取JSON中的
thumbURL、middleURL、objURL等字段获取图片地址。
流程图:Ajax请求获取图片数据流程
graph TD
A[构造Ajax请求URL] --> B[发送GET请求]
B --> C{响应状态码200?}
C -->|是| D[解析JSON数据]
C -->|否| E[输出错误信息]
D --> F[提取图片URL列表]
E --> G[重试或退出]
3.2 HTML文档结构与BeautifulSoup解析
虽然Ajax接口能更高效地获取图片数据,但在某些场景下(如接口不可用或需解析页面结构时),仍需使用HTML解析技术。BeautifulSoup 是Python中用于解析HTML文档的强大库,特别适合处理不规则或嵌套结构的HTML内容。
3.2.1 安装与基本用法
安装命令:
pip install beautifulsoup4
基本用法示例:
from bs4 import BeautifulSoup
import requests
url = build_search_url("猫")
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# 输出HTML文档标题
print(soup.title.string)
逻辑分析:
requests.get获取HTML页面内容。BeautifulSoup初始化解析器,指定解析器为'html.parser'。soup.title.string提取页面标题。
3.2.2 CSS选择器与标签匹配技巧
BeautifulSoup支持CSS选择器语法,可以快速定位HTML节点。
示例:查找所有img标签
images = soup.find_all('img')
for img in images[:5]: # 打印前5个img标签
print(img)
示例:使用CSS选择器查找class为”imgbox”的元素
img_boxes = soup.select('.imgbox')
for box in img_boxes:
print(box)
表格:CSS选择器常用语法
| 语法 | 含义 |
|---|---|
tag |
标签名选择器(如 img ) |
.class |
class选择器(如 .imgbox ) |
#id |
id选择器(如 #main ) |
tag.class |
组合选择器(如 div.content ) |
tag[attr=value] |
属性选择器(如 img[src] ) |
3.2.3 多层嵌套结构的提取方法
百度图片搜索页面的HTML结构往往存在多层嵌套,需要通过逐层查找提取目标数据。
示例:提取嵌套结构中的img标签
# 假设结构为 div -> ul -> li -> span -> img
results = soup.find('div', {'class': 'imgbox'})
if results:
ul = results.find('ul')
if ul:
for li in ul.find_all('li'):
img = li.find('img')
if img and 'src' in img.attrs:
print(img['src'])
逻辑分析:
- 先定位外层
div容器。 - 逐层进入
ul→li→img。 - 提取
img的src属性。
3.3 图片链接提取策略
在获取HTML内容或Ajax返回的JSON数据后,下一步是提取图片链接。由于网页可能使用懒加载或防盗链技术,需要识别真实图片地址。
3.3.1 img标签的定位与src属性提取
示例:提取HTML中的所有img标签src地址
img_tags = soup.find_all('img')
image_urls = [img.get('src') for img in img_tags if img.get('src')]
print(image_urls[:5])
3.3.2 数据懒加载与真实图片地址识别
懒加载通常使用 data-src 或 srcset 属性代替 src ,需特别注意。
示例:识别懒加载图片的真实地址
lazy_images = soup.find_all('img', {'data-src': True})
real_urls = [img['data-src'] for img in lazy_images]
print(real_urls[:5])
3.3.3 去重机制与链接存储结构设计
为了避免重复下载图片,需对链接进行去重处理。可使用集合或数据库存储已提取的链接。
示例:使用集合去重
seen_urls = set()
for url in real_urls:
if url not in seen_urls:
seen_urls.add(url)
print(f"新链接:{url}")
表格:链接存储结构设计建议
| 存储方式 | 优点 | 缺点 |
|---|---|---|
| 内存集合(set) | 快速去重,适合小规模数据 | 内存占用高,程序重启后丢失 |
| 文件存储(txt/json) | 持久化保存 | 读写速度慢 |
| SQLite数据库 | 结构化存储,支持查询与扩展 | 需要学习SQL语法 |
本章详细讲解了百度图片搜索接口的URL构造方法、HTML解析技术以及图片链接的提取策略。通过本章的学习,读者将掌握如何构造搜索URL、解析页面结构、识别懒加载图片,并实现链接的去重与存储。这些技能是构建百度图片爬虫系统的核心能力,也为后续章节的图片下载与项目整合奠定了坚实基础。
4. 图片下载、处理与本地存储方案
在完成百度图片的搜索与链接提取之后,下一步就是将图片从网络下载到本地,并进行必要的处理与存储。本章将围绕“下载—处理—存储”这一完整流程展开,重点介绍如何使用 Python 的 requests 和 PIL (现为 Pillow )库来完成图像的二进制下载、格式转换、尺寸调整、压缩等处理操作,并探讨几种可行的本地存储方案。
本章内容结构如下:
4.1 图片二进制数据的下载流程
在获取到图片的 URL 之后,需要通过 HTTP 请求将图片以二进制形式下载到本地。与普通文本内容不同,图片属于二进制数据,必须使用二进制模式进行读写。
4.1.1 使用requests获取图片流
requests 库支持以流式方式下载大文件,避免一次性加载到内存中。这对于图片下载尤其重要,因为图片文件可能较大。
import requests
def download_image(url, save_path):
try:
response = requests.get(url, stream=True, timeout=10)
if response.status_code == 200:
with open(save_path, 'wb') as f:
for chunk in response.iter_content(1024):
f.write(chunk)
print(f"Downloaded: {save_path}")
else:
print(f"Failed to download image from {url}, status code: {response.status_code}")
except Exception as e:
print(f"Error downloading image from {url}: {e}")
代码逻辑分析:
requests.get(url, stream=True):启用流式下载,避免一次性读取整个图片到内存中。response.iter_content(1024):每次读取 1024 字节的数据块,逐块写入文件。open(save_path, 'wb'):以二进制写入模式打开文件,确保数据正确写入。- 异常处理:捕获网络请求异常和文件写入错误,提高程序健壮性。
参数说明:
| 参数名 | 类型 | 描述 |
|---|---|---|
| url | str | 要下载的图片地址 |
| save_path | str | 本地保存路径(含文件名) |
4.1.2 二进制文件的写入操作
写入二进制文件时需要注意文件的打开模式。Python 中, 'wb' 表示以二进制写入模式打开文件,如果文件不存在则创建,存在则覆盖。
with open('image.jpg', 'wb') as f:
f.write(image_data)
逻辑分析:
with:确保文件在使用完后正确关闭,防止资源泄漏。'wb':二进制写入模式。image_data:从网络请求中获取的二进制内容。
4.1.3 文件命名策略与路径管理
为了避免图片命名重复,可以使用哈希值或时间戳进行命名。同时,为了方便管理,建议将图片按类别或时间分目录存储。
import os
import hashlib
def generate_safe_filename(url):
hash_obj = hashlib.md5(url.encode())
return f"{hash_obj.hexdigest()}.jpg"
def save_image_with_category(url, base_dir, category):
filename = generate_safe_filename(url)
category_dir = os.path.join(base_dir, category)
os.makedirs(category_dir, exist_ok=True)
save_path = os.path.join(category_dir, filename)
download_image(url, save_path)
参数说明:
| 参数名 | 类型 | 描述 |
|---|---|---|
| url | str | 图片链接 |
| base_dir | str | 存储根目录 |
| category | str | 分类名称,如“汽车”、“风景”等 |
文件结构示例:
images/
├── 汽车/
│ └── a1b2c3d4e5f67890.jpg
├── 风景/
│ └── 0f9e8d7c6b5a4321.jpg
4.2 图像处理与优化(PIL库)
下载的图片可能需要进行缩放、格式转换、压缩等处理操作。Python 中最常用的图像处理库是 Pillow (即 PIL 的现代分支)。
4.2.1 PIL库的安装与基本操作
安装 Pillow:
pip install pillow
基本使用示例:
from PIL import Image
img = Image.open('image.jpg')
img.show()
逻辑分析:
Image.open():加载图片文件。img.show():使用系统默认图像查看器显示图片。
4.2.2 图像尺寸调整与格式转换
调整图像尺寸和转换格式是图像处理中的常见需求,比如将大图缩略为 800x600 像素,或将 .png 转换为 .jpg 。
def resize_image(input_path, output_path, size=(800, 600)):
with Image.open(input_path) as img:
resized_img = img.resize(size)
resized_img.save(output_path)
逻辑分析:
img.resize(size):按指定尺寸缩放图片。resized_img.save(output_path):保存处理后的图片。
参数说明:
| 参数名 | 类型 | 描述 |
|---|---|---|
| input_path | str | 原始图片路径 |
| output_path | str | 处理后图片保存路径 |
| size | tuple(int) | 缩放尺寸 (width, height) |
4.2.3 图像质量压缩与水印去除
对于存储空间有限的场景,可以对图片进行质量压缩;同时,部分图片可能带有水印,可以通过裁剪或模糊处理去除。
def compress_image(input_path, output_path, quality=85):
with Image.open(input_path) as img:
img.save(output_path, optimize=True, quality=quality)
逻辑分析:
optimize=True:启用图像优化。quality=85:设置压缩质量(0-100,数值越小越压缩,画质越差)。
参数说明:
| 参数名 | 类型 | 描述 |
|---|---|---|
| input_path | str | 原始图片路径 |
| output_path | str | 压缩后图片保存路径 |
| quality | int | 压缩质量等级 |
4.3 存储方案与数据管理
在图片下载与处理完成后,需要选择合适的存储方式,并设计良好的数据管理机制,以提高系统的可维护性与扩展性。
4.3.1 本地文件系统存储
最简单的存储方式是将图片直接保存到本地磁盘。优点是实现简单,缺点是难以进行高效查询和管理。
graph TD
A[图片URL] --> B[发起HTTP请求]
B --> C[下载图片二进制流]
C --> D[写入本地文件]
D --> E[保存路径结构化]
推荐目录结构:
images/
├── 2024-10/
│ ├── cat/
│ └── dog/
├── 2024-11/
│ ├── car/
│ └── landscape/
4.3.2 SQLite数据库存储图片元数据
为了方便管理,可以使用 SQLite 数据库存储图片的元数据(如 URL、分类、下载时间、文件路径等),而图片本身仍保存在本地文件系统中。
CREATE TABLE images (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
category TEXT,
file_path TEXT,
download_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
插入记录示例:
import sqlite3
def save_metadata_to_db(url, category, file_path):
conn = sqlite3.connect('images.db')
cursor = conn.cursor()
cursor.execute('''
INSERT INTO images (url, category, file_path)
VALUES (?, ?, ?)
''', (url, category, file_path))
conn.commit()
conn.close()
参数说明:
| 参数名 | 类型 | 描述 |
|---|---|---|
| url | str | 图片原始链接 |
| category | str | 图片分类标签 |
| file_path | str | 本地存储路径 |
4.3.3 存储性能优化与目录结构设计
随着图片数量增加,单目录下文件过多会导致访问性能下降。可以采用以下优化策略:
- 按时间分目录 :每天/每月建立子目录。
- 按哈希值分目录 :将图片哈希值前两位作为目录名。
- 使用缓存机制 :缓存最近访问的图片路径,减少磁盘查找。
def get_hashed_dir(base_dir, url):
hash_val = hashlib.md5(url.encode()).hexdigest()
dir_name = hash_val[:2]
return os.path.join(base_dir, dir_name)
优化目录结构示例:
images/
├── a1/
│ └── a1b2c3d4e5f67890.jpg
├── 0f/
│ └── 0f9e8d7c6b5a4321.jpg
优点:
- 提高文件系统查找效率。
- 避免单目录文件过多导致的性能瓶颈。
- 便于后续扩展为分布式存储结构。
总结回顾
本章从“图片下载—处理—存储”三个关键环节入手,系统性地介绍了如何使用 Python 实现图片爬取的后续流程。通过 requests 库实现了高效的二进制图片下载,借助 Pillow 进行了图像的尺寸调整、格式转换与压缩,最后设计了本地文件系统与 SQLite 数据库相结合的存储策略,并提出了性能优化方案。
下一章将在此基础上,介绍如何通过多线程并发提升爬虫效率,并整合所有模块构建一个完整的百度图片爬虫项目。
5. 多线程加速与完整爬虫项目实战
5.1 并发爬取的基本原理
在爬虫项目中,为了提高效率,我们通常会采用并发机制来加速数据的获取。单线程爬虫虽然结构简单,但效率低下,特别是在面对大量请求时。而多线程机制可以充分利用CPU资源,同时处理多个网络请求,显著缩短爬取时间。
5.1.1 单线程与多线程效率对比
| 特性 | 单线程 | 多线程 |
|---|---|---|
| 执行方式 | 顺序执行 | 并行/并发执行 |
| 资源利用率 | 低 | 高 |
| 适用场景 | 小规模任务 | 大规模、高并发任务 |
| 编程复杂度 | 简单 | 中等 |
在Python中,由于GIL(全局解释器锁)的存在,多线程在CPU密集型任务中并不能真正并行。但在I/O密集型任务中,如网络请求,多线程仍能显著提升效率。
5.1.2 Python中的并发模型(threading与asyncio)
Python中常用的并发模型包括:
threading:多线程模型,适用于I/O密集型任务。asyncio:异步IO模型,基于事件循环和协程,适用于高并发场景。
以下是一个使用 threading 的简单示例:
import threading
import time
def fetch_url(url):
print(f"开始请求 {url}")
time.sleep(1) # 模拟网络延迟
print(f"完成请求 {url}")
urls = ['http://example.com/page1', 'http://example.com/page2', 'http://example.com/page3']
threads = []
for url in urls:
thread = threading.Thread(target=fetch_url, args=(url,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print("所有请求完成")
参数说明:
target:线程执行的目标函数。args:传递给目标函数的参数,必须为元组。start():启动线程。join():等待所有线程执行完毕。
5.1.3 使用concurrent.futures实现并发控制
concurrent.futures 模块提供了一个高级接口来实现线程池或进程池的并发执行。
from concurrent.futures import ThreadPoolExecutor
import time
def fetch_url(url):
print(f"开始请求 {url}")
time.sleep(1)
print(f"完成请求 {url}")
return url
urls = ['http://example.com/page1', 'http://example.com/page2', 'http://example.com/page3']
with ThreadPoolExecutor(max_workers=3) as executor:
results = executor.map(fetch_url, urls)
print("所有任务完成")
代码说明:
ThreadPoolExecutor:线程池执行器,max_workers表示最大并发线程数。executor.map():将任务分配给线程池执行,按顺序返回结果。
5.2 百度图片爬虫项目整合
在完成基本的并发控制后,我们可以将前面章节中的功能整合成一个完整的百度图片爬虫项目。
5.2.1 项目结构划分与模块化设计
一个完整的项目结构如下:
baidu_image_crawler/
├── crawler/
│ ├── __init__.py
│ ├── request_handler.py
│ ├── parser.py
│ ├── downloader.py
│ └── scheduler.py
├── config/
│ └── settings.py
├── logs/
│ └── crawler.log
├── main.py
└── requirements.txt
request_handler.py:处理HTTP请求。parser.py:解析HTML并提取图片链接。downloader.py:图片下载与本地存储。scheduler.py:任务调度与并发控制。settings.py:配置参数。main.py:程序入口。
5.2.2 关键函数与类的封装
以 downloader.py 为例,封装图片下载类:
import os
import requests
from urllib.parse import urlsplit
class ImageDownloader:
def __init__(self, save_dir="images"):
self.save_dir = save_dir
os.makedirs(self.save_dir, exist_ok=True)
def download_image(self, url):
try:
response = requests.get(url, timeout=10)
filename = os.path.join(self.save_dir, os.path.basename(urlsplit(url).path))
with open(filename, 'wb') as f:
f.write(response.content)
print(f"已保存图片: {filename}")
except Exception as e:
print(f"下载失败: {url}, 错误: {e}")
功能说明:
- 自动创建图片存储目录。
- 使用
urlsplit提取URL路径生成文件名。 - 捕获异常并输出错误信息。
5.2.3 日志记录与爬虫状态监控
可使用 logging 模块记录爬虫运行日志:
import logging
logging.basicConfig(
filename='logs/crawler.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def log_info(message):
logging.info(message)
print(message)
这样可以在控制台和日志文件中同时记录爬虫状态,便于后期排查问题。
5.3 爬虫项目的部署与运行
5.3.1 配置文件与参数化运行
config/settings.py 示例:
SEARCH_KEYWORD = "猫咪"
MAX_PAGES = 5
IMAGES_PER_PAGE = 30
SAVE_DIR = "downloaded_images"
MAX_WORKERS = 10
在 main.py 中引用配置参数:
from config.settings import SEARCH_KEYWORD, MAX_PAGES, SAVE_DIR
from crawler.scheduler import run_crawler
if __name__ == "__main__":
run_crawler(keyword=SEARCH_KEYWORD, max_pages=MAX_PAGES, save_dir=SAVE_DIR)
这样可灵活配置爬虫行为,无需修改代码即可调整运行参数。
5.3.2 定时任务与自动化执行
可以使用 cron (Linux)或任务计划器(Windows)定期运行爬虫。
Linux示例(每天凌晨1点执行):
0 1 * * * /usr/bin/python3 /path/to/main.py
或者使用 APScheduler 实现代码级定时任务:
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
def job():
print(f"定时任务执行时间: {datetime.now()}")
run_crawler()
scheduler = BlockingScheduler()
scheduler.add_job(job, 'cron', hour=1)
scheduler.start()
5.3.3 常见问题排查与性能调优
| 问题类型 | 表现 | 解决方案 |
|---|---|---|
| 请求被拒绝 | 返回403或429错误 | 增加User-Agent,设置请求间隔 |
| 图片链接失效 | 下载时出现404 | 增加重试机制,过滤无效链接 |
| 存储路径错误 | 写入失败 | 检查目录权限,使用绝对路径 |
| 内存占用过高 | 多线程下载时OOM | 限制线程数,使用流式下载 |
| 性能瓶颈 | 下载速度慢 | 优化并发策略,使用代理服务器 |
通过日志分析、性能测试和参数调整,逐步优化爬虫项目的稳定性和效率。
简介:在Python编程中,网络爬虫是获取网络数据的重要手段,尤其在图像数据采集方面具有广泛应用。本文详细讲解如何使用Python实现百度图片的自动爬取,涵盖网络爬虫基础原理、requests库发送HTTP请求、BeautifulSoup解析HTML内容、图片链接提取与下载、多线程加速以及图片处理技术(如PIL库)。通过本教程,读者可掌握完整的图片爬虫开发流程,并为后续数据挖掘、图像分析等任务打下坚实基础。
更多推荐



所有评论(0)