Python爬虫入门:从零构建图片爬虫,掌握requests与BeautifulSoup实战 1. 从零开始为什么选择Python作为你的第一把“爬虫铲子”如果你在网上冲浪时看到一张心仪的壁纸、一组精美的设计素材或者想批量保存某个教程里的所有插图手动一张张右键另存为不仅效率低下还容易出错。这时候一个简单的图片爬虫就能解放你的双手。而Python无疑是踏入这个领域最友好、最强大的工具。为什么是Python首先它的语法极其接近自然语言即使你没有任何编程基础也能在短时间内看懂基本的爬虫代码在做什么。其次Python拥有一个庞大而活跃的社区这意味着你遇到的几乎所有问题都能在网上找到现成的解决方案或讨论。最重要的是针对网络爬虫这个特定任务Python有像requests、BeautifulSoup这样简单易用且功能强大的“神器”库让你无需从零造轮子可以专注于核心逻辑。很多人一听到“爬虫”就觉得高深莫测联想到复杂的反爬机制、法律风险甚至觉得这是黑客行为。其实不然。我们这里讨论的“简单爬取”核心是自动化地访问公开的网页并按照网页的规则HTML结构提取其中公开的图片链接然后下载到本地。这和你用浏览器打开网页查看网页源代码找到图片地址再下载在本质上是一样的只是由程序自动完成。关键在于你的目标必须是公开可访问且不违反网站robots.txt协议的内容。对于个人学习、收藏公开的美图、备份自己的社交媒体图片等非商业用途在合理范围内使用是没问题的。所以别被吓到。接下来我将带你用最少的代码实现一个能真正跑起来的图片爬虫。我们不会涉及复杂的登录、验证码破解或动态JS渲染就从最基础的静态网页图片抓取开始让你在十分钟内看到成果建立信心。2. 工欲善其事搭建你的Python爬虫工作台在开始写代码之前我们需要一个能运行Python代码的环境。对于新手我强烈推荐使用Anaconda来管理Python环境并用VSCode作为代码编辑器。这个组合能最大程度避免环境冲突和配置麻烦。2.1 安装Python告别“Command Not Found”如果你在命令行输入python --version没有任何反应或者版本很老比如2.7那么第一步就是安装Python。为什么选择Anaconda而不是官网直接下载直接从Python官网下载安装当然可以但Anaconda是一个集成了Python和大量科学计算、数据分析库包括我们马上要用到的的发行版。它自带一个叫conda的包和环境管理器可以让你为不同项目创建独立的Python环境避免库版本冲突。比如项目A需要requests的旧版本而项目B需要新版本用conda可以轻松切换互不干扰。安装步骤访问Anaconda官网搜索“Anaconda Distribution”下载对应你操作系统Windows/macOS/Linux的安装程序。建议选择Python 3.x版本的安装包。运行安装程序。在Windows上安装时务必勾选“Add Anaconda to my PATH environment variable”将Anaconda添加到系统PATH。这个选项有时默认不勾选如果不勾选后续在命令行使用python或conda命令可能会找不到。安装完成后打开你的终端Windows上是“命令提示符”或“Anaconda Prompt”macOS/Linux是“终端”。输入python --version并回车。如果显示类似Python 3.9.13的版本信息恭喜你安装成功。再输入conda --version确认conda也已就位。2.2 配置VSCode你的代码书写画布VSCodeVisual Studio Code是一款免费、轻量且功能强大的源代码编辑器对Python的支持非常友好。安装VSCode去官网下载安装即可。安装Python扩展打开VSCode点击左侧活动栏的“扩展”图标或按CtrlShiftX在搜索框中输入“Python”找到由Microsoft发布的“Python”扩展点击安装。选择解释器在VSCode中打开或新建一个文件夹作为你的项目目录。按CtrlShiftP打开命令面板输入 “Python: Select Interpreter” 并选择。你应该能看到一个路径包含“Anaconda”或“conda”的Python解释器选择它。这确保了VSCode会使用我们刚安装的Anaconda环境来运行代码。至此你的开发环境就准备好了。你可以在这个项目文件夹里新建一个文件例如命名为simple_image_spider.py我们接下来的所有代码都将在这里编写。2.3 安装必备库获取你的“爬虫工具包”我们的简单爬虫主要依赖两个库requests用于网络请求BeautifulSoup4用于解析HTML。打开终端Windows用户可以使用“Anaconda Prompt”确保你位于之前设置的项目目录下或者任意目录都可以因为库是全局或环境级安装的。输入以下命令pip install requests beautifulsoup4pip是Python的包安装工具Anaconda已经自带。这条命令会从Python的官方包索引中下载并安装这两个库及其依赖。注意有时网络原因可能导致pip安装缓慢或失败。你可以考虑使用国内的镜像源来加速例如清华源。命令可以替换为pip install requests beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以在Python交互环境命令行输入python进入里输入import requests和from bs4 import BeautifulSoup试试如果没有报错说明库已成功安装。3. 核心武器拆解requests与BeautifulSoup是如何工作的在动手写爬虫之前花几分钟理解这两个核心工具的工作原理能让你在出问题时更快地定位和解决而不是盲目地复制粘贴代码。3.1 requests你的“网络浏览器引擎”你可以把requests库想象成一个没有界面的、可编程的浏览器核心。它的主要工作是帮你发送HTTP请求到目标网址URL并把服务器返回的响应内容带回来。一个最简单的requests使用如下import requests url ‘https://example.com‘ response requests.get(url) # 发送一个GET请求 print(response.text) # 打印服务器返回的HTML文本内容关键对象是response。它包含了很多信息response.status_code: 状态码200表示成功404表示未找到500服务器错误等。每次请求后检查状态码是个好习惯。response.text: 服务器返回的文本内容对于网页就是HTML代码。response.content: 服务器返回的二进制内容对于图片、视频等非文本文件我们需要用这个属性来获取。response.headers: 响应头包含服务器返回的一些元信息。为什么不用浏览器直接查看源代码因为浏览器会将HTML渲染成漂亮的页面而requests拿到的是最原始的“原料”。爬虫需要的就是这份原始原料才能从中提取结构化的数据。3.2 BeautifulSoup你的“HTML信息提取器”拿到一长串杂乱无章的HTML代码后如何精准地找到图片标签并提取其中的链接src属性这就是BeautifulSoup常简写为bs4的用武之地。BeautifulSoup会将HTML文本解析成一棵“树形结构”称为DOM树并提供了非常直观的方法来遍历和搜索这棵树上的节点。from bs4 import BeautifulSoup html_doc “““htmlbodyimg src“image1.jpg“/p一些文字/pimg src“image2.png“//body/html””” soup BeautifulSoup(html_doc, ‘html.parser‘) # 使用html.parser解析器进行解析 images soup.find_all(‘img‘) # 找到所有的img标签 for img in images: print(img[‘src‘]) # 打印每个img标签的src属性值BeautifulSoup(html_doc, ‘html.parser‘): 构造一个Soup对象。第二个参数是指定解析器html.parser是Python自带的对于简单任务足够用。你也可以安装lxml解析器pip install lxml它速度更快、容错性更好。soup.find_all(‘img‘): 这是最常用的方法之一它返回一个列表包含了文档中所有名为img的标签。img[‘src‘]: 通过类似字典的方式获取标签的某个属性值。两者的分工非常明确requests负责“取”BeautifulSoup负责“析”。理解了这一点爬虫的基本流程就清晰了请求网页 - 解析HTML - 定位目标数据 - 提取并保存。4. 实战第一步爬取单个网页上的所有图片理论说得再多不如动手一试。我们找一个图片素材丰富的公开网站作为示例目标。请注意在实际操作中务必尊重网站的robots.txt协议并控制请求频率避免对目标网站造成压力。这里我们以一个允许爬取的公开图库示例页面为例在实际编写时请替换为合适的、你有权爬取的公开网址。假设我们要爬取的页面URL是https://example.com/gallery请替换为真实地址。4.1 编写爬取与解析代码让我们一步步构建脚本import requests from bs4 import BeautifulSoup import os # 用于创建目录 # 1. 定义目标URL和请求头 url ‘https://example.com/gallery‘ # 替换成你的目标网址 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‘ } # 设置User-Agent是为了让请求看起来更像一个普通的浏览器访问有些网站会对没有User-Agent的请求进行限制。 # 2. 发送GET请求 try: response requests.get(url, headersheaders) response.raise_for_status() # 如果状态码不是200会抛出HTTPError异常 except requests.exceptions.RequestException as e: print(f“请求出错 {e}“) exit() # 3. 解析HTML内容 soup BeautifulSoup(response.text, ‘html.parser‘) # 4. 查找所有图片标签 # 网页中的图片通常由 img 标签定义图片链接存储在 ‘src‘ 或 ‘data-src‘ 属性中。 img_tags soup.find_all(‘img‘) print(f“在该页面找到了 {len(img_tags)} 个图片标签。“) # 5. 准备本地存储目录 save_dir ‘downloaded_images‘ if not os.path.exists(save_dir): os.makedirs(save_dir) # 6. 遍历图片标签下载图片 for i, img_tag in enumerate(img_tags): # 获取图片链接。优先取‘src‘如果没有则尝试‘data-src‘常见于懒加载图片 img_url img_tag.get(‘src‘) or img_tag.get(‘data-src‘) if not img_url: print(f“第{i1}个图片标签未找到有效链接跳过。“) continue # 处理可能遇到的相对路径链接 # 如果链接不是以http/https开头可能是相对路径需要拼接上基础URL if img_url.startswith(‘//‘): img_url ‘https:‘ img_url elif img_url.startswith(‘/‘): # 假设基础域名为url的域名部分 from urllib.parse import urljoin img_url urljoin(url, img_url) elif not img_url.startswith((‘http://‘, ‘https://‘)): # 其他形式的相对路径 from urllib.parse import urljoin img_url urljoin(url, img_url) # 尝试下载图片 try: img_response requests.get(img_url, headersheaders, streamTrue) # streamTrue用于流式下载大文件 img_response.raise_for_status() # 从URL中提取图片文件名如果没有扩展名则根据Content-Type生成 # 优先使用URL中最后的路径作为文件名 img_name os.path.basename(img_url) if not img_name or ‘.‘ not in img_name: # 如果无法从URL获取则根据内容类型生成 content_type img_response.headers.get(‘content-type‘, ‘‘) if ‘image/jpeg‘ in content_type: ext ‘.jpg‘ elif ‘image/png‘ in content_type: ext ‘.png‘ elif ‘image/gif‘ in content_type: ext ‘.gif‘ else: ext ‘.bin‘ # 未知二进制文件 img_name f‘image_{i}{ext}‘ else: # 清理文件名中的查询参数 img_name img_name.split(‘?‘)[0] # 完整的保存路径 save_path os.path.join(save_dir, img_name) # 写入文件 with open(save_path, ‘wb‘) as f: for chunk in img_response.iter_content(chunk_size8192): f.write(chunk) print(f“成功下载: {img_name}“) except Exception as e: print(f“下载 {img_url} 失败: {e}“)4.2 代码逐行解析与避坑指南请求头HeadersUser-Agent是必须的。有些网站会检查这个字段如果发现是Python的默认User-Agent如python-requests/2.28.1可能会拒绝服务或返回不同的内容。模拟一个常见浏览器的User-Agent能大大提高成功率。异常处理网络请求充满了不确定性。使用try...except包裹请求代码并使用response.raise_for_status()在状态码异常时主动抛出错误是好习惯。这能防止程序因一个请求失败而崩溃。图片链接的多样性src属性是最常见的。>import requests url ‘https://example.com/robots.txt‘ resp requests.get(url) print(resp.text)输出可能包含类似这样的内容User-agent: * Disallow: /private/ Disallow: /admin/ Allow: /public/这表示对所有爬虫*禁止抓取/private/和/admin/目录下的内容但允许抓取/public/目录。对于图片爬虫你需要特别留意是否有针对图片目录如/images/,/assets/的Disallow规则。是否有针对你使用的User-Agent如*或Googlebot的特殊规则。 如果robots.txt明确禁止抓取你目标目录的内容那么你应该停止爬取。5.2 实施礼貌的请求间隔即使网站允许爬取疯狂地、不间断地发送请求也会对目标服务器造成压力轻则导致你的IP被暂时封禁重则可能影响网站的正常运行。这种行为被称为“暴力爬取”是不可取的。因此我们需要在连续请求之间加入延迟。time.sleep()函数是最简单的实现方式。修改我们的下载循环部分在每次下载请求后暂停一下import time for i, img_tag in enumerate(img_tags): # ... (前面的图片链接处理和下载代码不变) ... print(f“成功下载: {img_name}“) # 在请求之间加入延迟模拟人类浏览行为 time.sleep(1) # 暂停1秒time.sleep(1)表示程序暂停1秒。对于小型、个人的爬虫1到3秒的间隔通常被认为是比较礼貌的。如果你需要爬取大量页面这个间隔可能需要更长。更进阶的做法是使用随机延迟让爬虫行为更难以被预测和识别import random time.sleep(random.uniform(0.5, 2.5)) # 随机暂停0.5到2.5秒牢记这两点查看robots.txt和添加请求延迟是你从“脚本小子”迈向一个负责任的数据获取者的关键一步。6. 应对常见反爬策略从简单开始防御即使是最简单的静态网页也可能设置了一些基础的反爬机制。我们的爬虫需要一点“伪装”才能更稳定地工作。6.1 完善请求头Headers我们之前只设置了User-Agent但一个真实的浏览器请求会携带更多的头部信息。我们可以模拟得更像一些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‘, ‘Accept‘: ‘text/html,application/xhtmlxml,application/xml;q0.9,image/webp,image/apng,*/*;q0.8‘, ‘Accept-Language‘: ‘zh-CN,zh;q0.9,en;q0.8‘, ‘Accept-Encoding‘: ‘gzip, deflate, br‘, ‘Connection‘: ‘keep-alive‘, ‘Upgrade-Insecure-Requests‘: ‘1‘, }Accept告诉服务器客户端可以处理哪些类型的响应。Accept-Language偏好语言。Accept-Encoding支持的压缩格式。Connection保持连接。Upgrade-Insecure-Requests处理HTTPS。对于简单的图片爬取User-Agent通常是最关键的但完整的头部信息能让你的请求看起来更“自然”。6.2 处理Cookie与简单会话有些网站可能会通过Cookie来跟踪会话或实现简单的访问控制。requests库的Session对象可以自动处理Cookie让你在多次请求间保持状态。import requests session requests.Session() # 可以预先设置一些所有请求共用的头部 session.headers.update({ ‘User-Agent‘: ‘你的User-Agent字符串‘ }) # 第一个请求可能会设置Cookie response1 session.get(‘https://example.com/page1‘) # 第二个请求会自动携带上一个请求获得的Cookie response2 session.get(‘https://example.com/page2‘)对于不需要登录的公开图片站Session可能不是必须的但了解它有益无害。6.3 识别和绕过简单的图片防盗链防盗链Hotlink Protection是网站防止其他网站直接引用自己图片链接的一种技术。服务器会检查HTTP请求头中的Referer字段。如果Referer不是来自本站则可能返回错误如403 Forbidden或一张替代图片如“此图片来自XX网站禁止外链”。应对方法不发送Referer在请求图片时将Referer头置空或设置为目标网站本身。img_headers headers.copy() # 复制通用的headers img_headers[‘Referer‘] url # 将Referer设置为图片所在页面的URL有时有效 # 或者直接删除Referer头 # img_headers.pop(‘Referer‘, None) img_response requests.get(img_url, headersimg_headers, streamTrue)使用Referer欺骗将Referer设置为目标网站的域名模拟从站内访问。需要注意的是有些防盗链策略可能更复杂会结合其他令牌Token或签名Signature。对于简单的公开网站上述方法通常有效。如果遇到更复杂的情况就需要分析网页加载图片时的网络请求看看是否有额外的验证参数。7. 项目优化与扩展让你的爬虫更健壮、更强大一个能跑起来的脚本只是一个开始。要让它在各种环境下稳定工作并能处理更复杂的任务我们需要对它进行优化和功能扩展。7.1 增加错误重试机制网络不稳定、服务器临时故障都可能导致单次请求失败。我们可以实现一个简单的重试逻辑。def download_image_with_retry(img_url, save_path, max_retries3): “““带重试机制的图片下载函数””” for attempt in range(max_retries): try: img_response requests.get(img_url, headersheaders, streamTrue, timeout10) # 增加超时设置 img_response.raise_for_status() with open(save_path, ‘wb‘) as f: for chunk in img_response.iter_content(chunk_size8192): f.write(chunk) return True # 下载成功 except (requests.exceptions.RequestException, IOError) as e: print(f“下载尝试 {attempt 1} 失败: {e}“) if attempt max_retries - 1: wait_time 2 ** attempt # 指数退避等待1, 2, 4秒... print(f“等待 {wait_time} 秒后重试...“) time.sleep(wait_time) else: print(f“下载 {img_url} 失败已达最大重试次数。“) return False # 下载失败 return False然后在主循环中调用这个函数success download_image_with_retry(img_url, save_path) if success: print(f“成功下载: {img_name}“)这个函数实现了“指数退避”重试策略失败后等待时间逐渐变长既给了服务器恢复的时间又避免了盲目快速重试。7.2 支持多页面爬取翻页一个图库通常有多页。我们需要分析网站的翻页规律。观察URL模式点击下一页观察浏览器地址栏URL的变化。常见模式1https://example.com/gallery?page2常见模式2https://example.com/gallery/2常见模式3通过“加载更多”按钮和AJAX请求这种情况稍复杂需要分析网络请求。修改代码实现循环爬取base_url ‘https://example.com/gallery?page‘ total_pages 5 # 假设你知道总页数或者通过解析第一页获取 for page in range(1, total_pages 1): page_url base_url str(page) print(f“正在爬取第 {page} 页: {page_url}“) # 发送请求解析页面下载图片的代码封装成函数更好 crawl_single_page(page_url) # 礼貌性延迟避免过快翻页 time.sleep(2)如果不知道总页数你可以在爬取每一页时解析页面中是否还有“下一页”的链接直到没有为止。7.3 图片去重与增量爬取如果多次运行脚本或者目标页面有重复图片我们可能不希望重复下载。一个简单的方法是使用MD5哈希值对图片内容进行去重import hashlib def get_file_md5(file_path): “““计算文件的MD5值””” hash_md5 hashlib.md5() with open(file_path, “rb“) as f: for chunk in iter(lambda: f.read(4096), b““): hash_md5.update(chunk) return hash_md5.hexdigest() # 在保存图片前可以检查本地是否已有相同MD5的文件 # 但这需要先下载才能计算更常见的做法是记录已下载图片的URL。更实用的增量爬取策略是记录已爬取的图片URL。我们可以将成功下载的图片URL保存到一个文件或简单的数据库中下次爬取前先加载这个记录跳过已经存在的URL。import json record_file ‘downloaded_urls.json‘ # 加载历史记录 try: with open(record_file, ‘r‘) as f: downloaded_urls set(json.load(f)) except FileNotFoundError: downloaded_urls set() # 在主循环中检查 if img_url in downloaded_urls: print(f“图片已下载跳过: {img_url}“) continue # 下载成功后记录URL downloaded_urls.add(img_url) # 定期或最后保存记录 with open(record_file, ‘w‘) as f: json.dump(list(downloaded_urls), f)这种方法简单有效适合个人项目。8. 从脚本到工具代码重构与模块化当功能越来越多把所有代码都堆在一个主文件里会变得难以维护。好的实践是将代码按功能拆分成模块。我们可以创建几个Python文件spider_core.py: 包含发送请求、解析页面的核心函数。image_downloader.py: 包含处理图片链接、下载、保存、去重等功能的函数和类。utils.py: 存放通用工具函数如处理URL、计算哈希、读写记录文件等。main.py: 主程序入口负责协调各个模块处理命令行参数比如指定起始URL、保存路径等。例如spider_core.py可能包含import requests from bs4 import BeautifulSoup def fetch_page(url, headers): “““获取页面HTML””” # ... 请求和异常处理逻辑 ... return response.text def parse_image_urls(html, base_url): “““从HTML中解析所有图片URL””” soup BeautifulSoup(html, ‘html.parser‘) img_tags soup.find_all(‘img‘) urls [] for img in img_tags: url img.get(‘src‘) or img.get(‘data-src‘) if url: # 处理相对路径 from urllib.parse import urljoin full_url urljoin(base_url, url) urls.append(full_url) return urls而main.py则会非常简洁from spider_core import fetch_page, parse_image_urls from image_downloader import download_image, record_url import time def main(): start_url ‘https://example.com/gallery‘ headers { ... } # 1. 抓取页面 html fetch_page(start_url, headers) # 2. 解析图片链接 image_urls parse_image_urls(html, start_url) # 3. 下载图片 for url in image_urls: if not record_url.is_downloaded(url): # 检查是否已下载 success download_image(url, ‘./images‘, headers) if success: record_url.mark_as_downloaded(url) time.sleep(1) if __name__ ‘__main__‘: main()这样的结构清晰、易于测试和维护也方便你未来在此基础上添加更复杂的功能比如并发下载、代理支持等。走到这里你已经不仅仅是一个会运行脚本的初学者了。你理解了爬虫的基本原理掌握了核心工具的使用知道了如何礼貌地爬取数据并能够处理一些常见的异常和反爬策略甚至开始思考代码的结构。这就是一个扎实的起点。接下来你可以尝试用这个框架去探索更复杂的网站在实践中遇到并解决新的问题你的爬虫技能也会随之不断增长。记住爬虫的世界里耐心、细心和对规则的尊重与技术能力同等重要。