简介:这份资源是面向Python爬虫初学者与数据分析入门者的实战源码包,围绕东京奥运会奖牌国家及地区旗帜图片的抓取与处理展开,帮助读者把网页请求、页面解析、图片下载与本地存储串成一条完整链路。包内以HTML与Jupyter Notebook两类文件为主,Notebook便于边运行边观察结果,HTML则用于呈现页面结构与解析目标,压缩包整体约54KB,轻量易上手。内容涉及requests发送HTTP请求、BeautifulSoup解析HTML、CSS选择器定位图片链接、PIL图像处理、CSV或JSON数据存储,以及异常处理与robots.txt合规意识等知识点,适合作为爬虫练手项目或课程案例。目前已有217人学习下载,读者可据此掌握从网页抓取到图片落盘的完整流程,并理解爬虫稳定运行与合规采集的基本思路。
1. 从零拆解旗帜图片爬取:一份能直接跑的源码包到底长什么样
做数据标注、做视觉素材库、做地理信息可视化的人,迟早会碰到同一个需求:手里要有一批干净、命名规范、尺寸统一的旗帜图片。手动一张张右键另存,几十张还能忍,上百张就是纯体力活,而且命名混乱、格式不一,后期清洗的时间比下载还长。这份「部分国家及地区旗帜图片数据爬取源代码.zip」解决的正是这个环节——它把「找到图源、批量拉取、按规则落盘」这条链路固化成了可复用的脚本,而不是一次性手工操作。
它适合三类人:一是需要快速搭建本地旗帜素材库的开发者;二是想拿一个真实可跑的小项目练手爬虫基础(请求、解析、下载、异常处理)的新手;三是做数据集预处理、需要把图片按统一命名归档的从业者。源码包本身不绑定特定平台,核心逻辑就是「请求页面 → 提取图片链接 → 下载到本地目录」,你拿到后改几个参数就能套用到别的图片采集场景。下面我按实际拆包的顺序,把这份源码怎么用、参数怎么调、哪里容易翻车讲清楚。
2. 源码结构与运行环境:先看清目录再动手
拿到压缩包别急着双击运行,先解压看结构。这类爬取源码包通常不会太复杂,但目录组织方式直接决定了你改代码时找不找得到地方。我拆过的同类包里,常见的组织是「入口脚本 + 配置区 + 输出目录」三块,有的会把请求头和下载逻辑拆成单独模块,有的全塞在一个文件里。先花两分钟确认结构,比后面满屏找变量强。
2.1 典型目录构成与各文件职责
解压后大概率会看到类似这样的布局(不同作者命名习惯不同,但职责大同小异):
flags_crawler/ ├── main.py # 入口,串联抓取与下载流程 ├── config.py # 或写在 main 顶部的常量区:URL、路径、并发数 ├── requirements.txt # 依赖清单,通常只有 requests / beautifulsoup4 ├── output/ # 图片落盘目录,可能预置了 .gitkeep └── README.md # 简短说明,别指望写得很全main.py是主战场,抓取逻辑、解析逻辑、保存逻辑基本都在这里。config.py或者文件顶部的常量区是你最该先看的地方——目标 URL、保存路径、请求超时、重试次数这些参数都在这儿。requirements.txt决定了你要装什么,这类图片爬取项目依赖通常很轻,requests负责发请求,beautifulsoup4或lxml负责解析 HTML,偶尔会用os、time、random这些标准库做路径处理和限速。
提示:如果包里没有
requirements.txt,直接看 import 语句,把第三方库名记下来手动装,别猜。
2.2 环境准备与依赖安装
环境这块不用搞太复杂,Python 3.7 以上都能跑。我一般习惯先建虚拟环境,避免和系统里其他项目的包版本打架:
# 创建并激活虚拟环境(Windows 用 venv\Scripts\activate) python -m venv venv source venv/bin/activate # 安装依赖,有 requirements.txt 就直接装 pip install -r requirements.txt # 没有清单文件时,手动装核心两个 pip install requests beautifulsoup4装完先别跑全量,用python -c "import requests, bs4; print('ok')"验证一下导入没问题。这一步看着多余,但我见过太多「跑一半报 ModuleNotFoundError」的情况,都是环境没激活或者装到了全局。参数上,requests版本建议 2.25 以上,老版本对某些 TLS 配置的兼容性差,容易在请求阶段就卡住。
2.3 关键配置项逐个说明
打开主脚本,重点盯这几个变量,它们决定了脚本能不能跑通、跑得快不快、会不会被封:
| 配置项 | 常见命名 | 作用 | 调整建议 |
|---|---|---|---|
| 目标地址 | BASE_URL/TARGET_URL | 图片列表所在页面 | 换成你要抓的页面 |
| 保存路径 | SAVE_DIR/OUTPUT_PATH | 图片落盘位置 | 用绝对路径,避免相对路径踩坑 |
| 请求头 | HEADERS | 伪装浏览器身份 | 至少带 User-Agent |
| 超时 | TIMEOUT | 单次请求等待上限 | 设 10~15 秒,太短易误判失败 |
| 延时 | DELAY/SLEEP | 请求间隔 | 0.5~2 秒,别设 0 |
请求头里最关键的是User-Agent,很多图源站点会直接拒绝没有 UA 的请求,返回 403。保存路径建议写绝对路径,相对路径在不同工作目录下执行时落盘位置会变,找图的时候容易懵。延时参数是很多人忽略的,设成 0 全速跑,短时间几十上百个请求打过去,轻则被限流,重则 IP 被临时封,后面全失败。
3. 抓取与下载核心逻辑:请求、解析、落盘三步走
这份源码的价值不在「能下载」,而在「下载得规整」。真正要理解的是它怎么把一堆散落的图片链接变成命名统一的本地文件。整个流程拆开就是三步:发请求拿页面、从页面里提取图片地址、按规则保存到本地。每一步都有细节,下面逐个拆。
3.1 发送请求与页面获取
第一步是拿到图片列表页的 HTML。核心代码通常长这样:
import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0 Safari/537.36" } def fetch_page(url, timeout=15): """请求页面,返回 HTML 文本;失败返回 None""" try: resp = requests.get(url, headers=HEADERS, timeout=timeout) resp.raise_for_status() # 非 2xx 状态码直接抛异常 resp.encoding = resp.apparent_encoding # 防止中文乱码 return resp.text except requests.RequestException as e: print(f"[请求失败] {url} -> {e}") return None这段逻辑的关键点有三个。headers里带 UA 是基本操作,缺了它很多站点直接返回 403。raise_for_status()让非 200 的响应立刻暴露出来,而不是拿着一个错误页面去解析,那样后面提取链接会得到空结果,你还以为是解析写错了。apparent_encoding是根据内容自动推断编码,比硬写utf-8稳,遇到 GBK 编码的页面不会变乱码。参数上timeout别省,网络抖动时没有超时设置,脚本会一直挂着,看起来像卡死。
3.2 解析页面提取图片链接
拿到 HTML 后,要从里面把图片地址抠出来。常见做法是用 BeautifulSoup 找img标签,取src属性:
from bs4 import BeautifulSoup from urllib.parse import urljoin def extract_image_urls(html, base_url): """从 HTML 中提取所有图片链接,补全为绝对地址""" soup = BeautifulSoup(html, "html.parser") urls = [] for img in soup.find_all("img"): src = img.get("src") or img.get("data-src") # 兼容懒加载 if not src: continue full_url = urljoin(base_url, src) # 相对路径转绝对 urls.append(full_url) return urls这里有两个容易翻车的点。一是懒加载,很多现代页面图片真实地址放在>import os import time import random def download_image(url, save_dir, filename): """下载单张图片并保存,返回是否成功""" os.makedirs(save_dir, exist_ok=True) filepath = os.path.join(save_dir, filename) try: resp = requests.get(url, headers=HEADERS, timeout=15, stream=True) resp.raise_for_status() with open(filepath, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) return True except requests.RequestException as e: print(f"[下载失败] {url} -> {e}") return False finally: time.sleep(random.uniform(0.5, 1.5)) # 随机延时,降低被封风险
命名规范是这份源码区别于「随手下载」的核心。常见做法是用图片 URL 的最后一段作为文件名,或者根据页面上的国家/地区名称重命名。stream=True配合iter_content是下载大图的标准姿势,避免一次性把整个响应读进内存。finally里的随机延时是血泪经验——固定间隔容易被识别出机器行为,随机化之后请求节奏更像人。参数上chunk_size设 8192 字节是通用值,太小写入频繁,太大内存占用高,一般不用改。
3.4 主流程串联与批量执行
把上面三步串起来,就是完整的批量抓取流程:
def main(): html = fetch_page(BASE_URL) if not html: print("首页获取失败,终止") return img_urls = extract_image_urls(html, BASE_URL) print(f"共提取到 {len(img_urls)} 张图片链接") success = 0 for idx, url in enumerate(img_urls, 1): ext = os.path.splitext(url)[1] or ".png" filename = f"flag_{idx:03d}{ext}" # 统一编号命名 if download_image(url, SAVE_DIR, filename): success += 1 print(f"完成:成功 {success} / 总计 {len(img_urls)}") if __name__ == "__main__": main()f"flag_{idx:03d}"这种格式化命名保证文件名定长、可排序,后期做数据集时按编号就能对应。ext从 URL 里取扩展名,取不到就默认.png,避免生成没有后缀的文件。整个主流程没有并发,是串行下载,速度不快但胜在稳定、好排查。想提速可以上线程池,但那是进阶操作,第一次跑建议先串行确认链路通。
4. 避坑与常见问题排查:这些坑我替你踩过了
爬取类脚本的坑,八成不在代码逻辑本身,而在网络环境、目标站点策略和文件系统上。下面这几条是我拆同类包时反复遇到的,按「现象 → 原因 → 解决」列出来,你对照排查能省不少时间。
4.1 请求返回 403 或 418
现象:脚本一跑就报 403 Forbidden,或者 418(I'm a teapot),一张图都下不下来。
原因:目标站点识别出请求不是来自真实浏览器。最常见的是没带User-Agent,或者带了但用的是python-requests/2.x这种默认 UA,一眼就被认出来。部分站点还会检查Referer、Accept-Language等头。
解决:把HEADERS补全,至少包含User-Agent、Referer(设成目标站点首页)、Accept-Language。UA 用真实浏览器的字符串,别用默认的。如果补全后还是 403,说明站点有更严格的校验,这时候要么放慢频率,要么换图源,别硬刚。
4.2 下载的图片打不开或只有几 KB
现象:文件确实生成了,但双击打不开,或者大小只有 1~2 KB。
原因:下载到的不是图片本身,而是错误页面或者占位图。常见于链接失效返回 404 页面、或者防盗链返回一张提示图。代码里如果没校验响应类型,就会把 HTML 错误页当成图片存下来。
解决:在download_image里加一层校验,检查resp.headers.get("Content-Type")是否以image/开头,不是就跳过并记录。另外可以在保存后检查文件大小,小于某个阈值(比如 1 KB)的删掉重试。
4.3 文件名乱码或路径报错
现象:保存时报OSError: [Errno 22] Invalid argument,或者文件名变成一堆问号。
原因:如果命名规则里用了页面上的国家/地区名称,而这些名称里可能含有 Windows 不允许的字符(\ / : * ? " < > |),或者编码不一致导致乱码。
解决:命名前做一次清洗,把非法字符替换成下划线。更稳的做法是干脆不用中文名,用编号或拼音命名,显示名称单独存一份映射表。路径统一用os.path.join拼接,别手写斜杠。
4.4 跑到一半卡住不动
现象:前几十张下得好好的,突然卡住,既不报错也不继续。
原因:某个请求没有超时设置,或者服务端保持连接不返回,脚本一直等。也可能是触发了限流,服务端故意拖慢响应。
解决:确保每个requests.get都带timeout参数。如果已经带了还卡,把超时调短到 10 秒,并在外层加一个总时长控制。限流的话,把DELAY调大,或者分批跑,跑一批停几分钟。
4.5 重复下载与断点续传
现象:脚本中断后重跑,已经下好的图片又下一遍,浪费时间还可能触发限流。
原因:代码没有检查本地是否已存在同名文件,每次都是无条件下载。
解决:在download_image开头加一句判断,if os.path.exists(filepath): return True,跳过已存在的文件。这样重跑就相当于断点续传,只补缺失的部分。注意如果图源更新了,这个逻辑会导致旧图不更新,按需取舍。
5. 进阶技巧:把这份源码改成你自己的采集器
跑通默认流程只是开始,这份源码真正的价值是它提供了一个可改造的骨架。下面几个改造方向,是我实际用下来最能提升效率的,按投入产出比排序。
5.1 用线程池把串行改成并发
串行下载一百张图,按每张 1 秒算就是一百多秒。改成并发能压到十几秒,代价是要控制好并发数,别把人家站点打挂:
from concurrent.futures import ThreadPoolExecutor, as_completed def batch_download(img_urls, save_dir, max_workers=5): """并发下载,max_workers 控制同时进行的请求数""" success = 0 with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {} for idx, url in enumerate(img_urls, 1): ext = os.path.splitext(url)[1] or ".png" filename = f"flag_{idx:03d}{ext}" futures[executor.submit(download_image, url, save_dir, filename)] = url for future in as_completed(futures): if future.result(): success += 1 return successmax_workers是关键参数,设 5 是保守值,对大多数站点够用又不至于太激进。设成 20、50 的,基本等于在告诉对方「我是脚本」,很容易被限流甚至封 IP。并发下载时time.sleep的随机延时依然要保留,只是它现在发生在各个线程内部,整体节奏还是被拉住了。
5.2 增加重试机制与失败日志
网络请求失败是常态,一次失败就放弃太浪费。加个重试,把失败记录单独存下来,方便事后补:
def download_with_retry(url, save_dir, filename, retries=3): """带重试的下载,全部失败后写入失败日志""" for attempt in range(1, retries + 1): if download_image(url, save_dir, filename): return True print(f" 第 {attempt} 次失败,重试...") time.sleep(2 * attempt) # 退避等待,越往后等越久 with open("failed.log", "a", encoding="utf-8") as f: f.write(url + "\n") return False退避等待用2 * attempt,第一次失败等 2 秒,第二次等 4 秒,给服务端喘息时间。失败日志单独存,跑完后打开看一眼,如果失败集中在某几个链接,多半是那几个链接本身失效了,不用反复重试。
5.3 图片去重与格式统一
采集来的图片可能有重复(同一张图不同尺寸、不同格式),后期做数据集时要去重。简单做法是按文件内容的哈希值判断:
import hashlib def file_hash(filepath): """计算文件 MD5,用于去重""" h = hashlib.md5() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): h.update(chunk) return h.hexdigest() def deduplicate(save_dir): """扫描目录,删除内容重复的文件""" seen = {} removed = 0 for name in os.listdir(save_dir): path = os.path.join(save_dir, name) if not os.path.isfile(path): continue h = file_hash(path) if h in seen: os.remove(path) removed += 1 else: seen[h] = name print(f"去重完成,删除 {removed} 个重复文件")格式统一则可以用 Pillow 批量转成 PNG 或 JPG,顺便统一尺寸。这一步不是必须的,但如果你要把图片喂给模型或者做展示,统一格式能省掉后面很多麻烦。
5.4 验证采集结果是否完整
跑完之后别急着关终端,花一分钟验证结果。我一般会做三件事:数文件数量对不对得上预期、抽查几张图能不能正常打开、检查有没有 0 字节文件。命令行下可以快速统计:
# 统计文件数量和总大小 ls -l output/ | wc -l du -sh output/ # 找出小于 1KB 的可疑文件 find output/ -type f -size -1kfind那条命令能揪出下载不完整的文件,这些基本就是错误页面或者中断的下载。发现之后对照failed.log补跑就行。从那以后我每次跑完采集脚本,都强制走一遍「数量核对 + 小文件排查 + 随机抽检」这三步,宁可多花两分钟,也不想在后期用数据时才发现混进去一堆坏图。希望这份拆解帮到你,拿到源码包后先按默认配置跑通,再按自己的需求改。
本文还有配套的精品资源,点击获取