ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Python爬虫学习框架:从urllib到Scrapy的完整路线与避坑指南

Python爬虫学习框架:从urllib到Scrapy的完整路线与避坑指南 简介这份PDF面向零基础到进阶的Python爬虫学习者系统梳理了从入门到实战的完整学习框架帮助初学者解决「学什么、怎么学、在哪里学」的困惑。内容围绕爬虫三部曲展开获取数据环节讲解urllib、requests、aiohttp等请求库及异步抓取思路解析数据环节覆盖BeautifulSoup、pyquery、XPath与正则表达式的定位技巧存储数据环节则涉及TXT、JSON、CSV及MySQL、MongoDB等方案并延伸至代理IP、请求头伪装、Selenium动态渲染与分布式爬虫等进阶话题同时提醒Robots协议与合规边界。资源为单个PDF文件压缩包约2.1MB轻量便携适合随时查阅。目前已有174人学习可作为爬虫知识体系搭建与查漏补缺的参考框架。1. 从一份学习框架说起为什么有人两周上手有人卡在环境配置很多人第一次接触 Python 爬虫都是从收藏一份「完整学习框架」开始的。这份《2023年最新Python爬虫的完整学习框架.pdf》就是典型代表——它不是某个具体项目的源码包而是一张把「请求库、解析库、存储方案、工程化框架」串起来的学习路线图。我见过太多人把这类 PDF 存进网盘吃灰也见过有人照着框架两周就能稳定抓取主流站点数据。差别不在天赋在于有没有把框架里的每个节点落到可运行的代码上。这份资料解决的核心问题是告诉你学什么、按什么顺序学、每个阶段用什么库。它把爬虫拆成「获取数据 → 解析数据 → 存储数据」三部曲再往上延伸到 Scrapy、PySpider、Nutch 这类工程化框架。适合两类人一是刚学完 Python 基础语法、想找个明确路径的初学者二是写过零散脚本、但没形成体系、遇到反爬就翻车的半熟手。如果你指望它是一份「复制粘贴就能跑」的代码合集那会失望但如果你想少走弯路、知道每个阶段该翻哪份文档这份框架的参考价值很高。2. 请求层选型urllib、requests、aiohttp 到底先学哪个2.1 从标准库 urllib 起步的理由框架里明确建议先从 Python 自带的 urllib 开始这个顺序是有道理的。urllib 是内置的 HTTP 请求库不需要额外安装能让你直接看到请求构造的底层细节——URL 编码、请求头设置、超时处理、异常捕获。很多人一上来就用 requests写起来确实爽但遇到编码问题、重定向异常时完全不知道底层发生了什么。用 urllib 发一个最基础的 GET 请求代码长这样import urllib.request import urllib.parse # 构造请求头模拟浏览器 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 } # 目标 URL带查询参数 params {q: python爬虫, page: 1} url https://example.com/search? urllib.parse.urlencode(params) # 构造 Request 对象并发送 req urllib.request.Request(url, headersheaders) try: with urllib.request.urlopen(req, timeout10) as resp: html resp.read().decode(utf-8) print(html[:500]) except urllib.error.HTTPError as e: print(fHTTP错误: {e.code}) except urllib.error.URLError as e: print(fURL错误: {e.reason})这段代码的关键点有三个urlencode负责把字典转成合法的查询字符串避免手动拼接出错Request对象承载请求头User-Agent是最基础的伪装timeout参数必须设否则遇到不响应的服务器会一直挂着。参数方面timeout10是秒数一般设 5 到 15 之间太短容易误判超时太长会拖慢整体节奏。2.2 requests 的简洁与 aiohttp 的异步提速当你用 urllib 写过十几个请求之后切到 requests 会明显感觉到效率提升。requests 自带 JSON 解析、会话保持、编码自动识别同样的请求三行就能搞定import requests session requests.Session() session.headers.update({ 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 }) resp session.get(https://example.com/api/list, params{page: 1}, timeout10) data resp.json() # 自动解析 JSON print(data.get(items, [])[:3])Session对象的好处是复用 TCP 连接和请求头批量请求时比每次新建连接快不少。resp.json()直接返回字典省去手动json.loads。但同步请求的天花板很明显。框架里提到一个数据请求同一个页面 100 次同步方式最少 30 秒异步方式只需 3 秒左右。这个差距在批量抓取时就是能不能跑完的区别。aiohttp 配合async/await写法能把并发请求压到很低的时间成本import aiohttp import asyncio async def fetch(session, url): async with session.get(url, timeoutaiohttp.ClientTimeout(total10)) as resp: return await resp.text() async def main(urls): async with aiohttp.ClientSession() as session: tasks [fetch(session, u) for u in urls] results await asyncio.gather(*tasks) return results urls [fhttps://example.com/page/{i} for i in range(1, 21)] htmls asyncio.run(main(urls)) print(f抓取完成共 {len(htmls)} 页)asyncio.gather把 20 个请求同时发出去总耗时接近最慢的那一个而不是累加。注意ClientTimeout要显式设置aiohttp 默认超时是 5 分钟不设的话一个卡住的请求会拖死整批任务。常见做法是把并发数控制在 10 到 50 之间太高容易被目标站点限流。提示异步爬虫的调试比同步麻烦异常堆栈不直观。建议先用同步版本跑通逻辑再改写成异步。3. 解析层实战BeautifulSoup、lxml、pyquery 与正则的边界3.1 四种解析方式的适用场景拿到 HTML 之后下一步是从中提取目标数据。框架里列了四种主流方案BeautifulSoup、pyquery、XPath配合 lxml、正则表达式。它们不是互相替代的关系而是各有各的舒适区。BeautifulSoup 的优势是容错性强遇到标签不闭合、属性缺失的脏 HTML 也能解析而且自动处理编码转换。缺点是速度偏慢大文档解析时明显。pyquery 用 jQuery 风格的 CSS 选择器前端基础好的人上手极快底层用 lxml 解析器速度比 BeautifulSoup 快。XPath 是功能最全的支持超过 100 个内置函数能做字符串处理、数值比较、节点定位适合结构复杂的页面。正则表达式速度最快但写起来最费劲页面结构一变就得重写。我一般会这样选页面结构规整、需要快速出结果用 pyquery页面脏乱、标签不规范用 BeautifulSoup需要按文本内容、属性值做复杂筛选用 XPath只需要提取几个固定格式的字符串比如电话号码、日期用正则。3.2 用 pyquery 提取列表数据的完整示例假设要抓一个商品列表页每个商品有标题、价格、链接。用 pyquery 的写法from pyquery import PyQuery as pq import requests html requests.get(https://example.com/products, timeout10).text doc pq(html) items [] for li in doc(.product-list .item).items(): title li.find(.title).text().strip() price li.find(.price).attr(data-price) # 从属性取价格 link li.find(a).attr(href) if title and price: items.append({title: title, price: price, link: link}) print(f共提取 {len(items)} 条) for it in items[:3]: print(it)doc(.product-list .item)是 CSS 选择器层级关系用空格表示。.items()返回可迭代对象逐个处理。.text()取文本内容.attr()取属性值。这里有个细节价格放在>from lxml import etree html requests.get(https://example.com/products, timeout10).text tree etree.HTML(html) # XPath筛选>import csv data [ {title: 商品A, price: 199, link: https://example.com/a}, {title: 商品B, price: 299, link: https://example.com/b}, ] with open(products.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, price, link]) writer.writeheader() writer.writerows(data)encodingutf-8-sig是为了让 Excel 打开时不乱码这个坑我踩过好几次。newline防止 Windows 下多出空行。DictWriter按字典键写入字段顺序由fieldnames控制。4.2 MongoDB 存非结构化数据的优势当你要存的是评论、文章正文、图片链接这类长度不固定、结构不统一的数据时MongoDB 比 MySQL 更合适。它不需要预先定义表结构直接存 JSON 文档。用 PyMongo 操作from pymongo import MongoClient, errors client MongoClient(mongodb://localhost:27017/, serverSelectionTimeoutMS5000) db client[spider_db] col db[products] # 插入前先建唯一索引防止重复抓取 col.create_index(link, uniqueTrue) docs [ {title: 商品A, price: 199, link: https://example.com/a, tags: [数码, 热销]}, {title: 商品B, price: 299, link: https://example.com/b, tags: [家居]}, ] for doc in docs: try: col.update_one({link: doc[link]}, {$set: doc}, upsertTrue) except errors.PyMongoError as e: print(f写入失败: {e}) print(f当前集合文档数: {col.count_documents({})})update_one配合upsertTrue是幂等写入的关键——同一个链接重复抓取时更新而不是重复插入。create_index(link, uniqueTrue)在数据库层面兜底即使代码逻辑有漏洞也不会产生重复数据。serverSelectionTimeoutMS设短一点连不上时快速失败而不是干等。Redis 在框架里被定位为分布式爬虫的数据存储主要用来做请求队列去重和缓存。单机爬虫用不上但如果你要扩展到多台机器协同抓取Redis 的 set 结构做 URL 去重非常高效。5. 避坑与排查请求被拒、解析为空、数据重复的常见原因5.1 返回 403 或验证码页面现象请求返回 403 状态码或者拿到的是一个验证码页面而不是目标内容。原因最常见的是请求头缺少User-Agent或者User-Agent用了默认的 Python-urllib 标识。其次是请求频率太高触发了目标站点的限流。还有一种情况是目标站点检查了Referer或Cookie。解决补全请求头至少包含User-Agent、Referer、Accept-Language。降低请求频率在请求之间加time.sleep(random.uniform(1, 3))。如果目标站点需要登录态用Session保持 Cookie。框架里提到的 Selenium 方案适合处理 JavaScript 渲染后才能拿到数据的页面但它的开销比纯 HTTP 请求大得多能不用就不用。5.2 解析结果为空列表现象代码没报错但提取到的数据是空列表或空字符串。原因三种可能——选择器写错了数据是 JavaScript 动态加载的初始 HTML 里根本没有页面编码不是 UTF-8中文变成乱码导致匹配失败。解决先在浏览器里用CtrlU查看页面源代码不是 Elements 面板确认目标数据是否在初始 HTML 里。如果不在说明需要抓包分析真实请求或者用 Selenium 渲染。选择器问题可以用print(doc)打印前 500 个字符肉眼确认结构。编码问题检查resp.encoding必要时手动设为resp.apparent_encoding。5.3 数据重复写入现象数据库里出现大量重复记录同一个商品被存了多次。原因爬虫重跑时没有做去重判断或者去重逻辑只检查了标题而标题有细微差异。解决用唯一标识通常是 URL 或商品 ID做去重键。MongoDB 建唯一索引配合upsertTrueMySQL 用INSERT ... ON DUPLICATE KEY UPDATECSV 在写入前用 set 记录已处理的链接。不要依赖标题去重标题可能因为空格、标点差异导致判断失效。5.4 异步爬虫报「Event loop is closed」现象aiohttp 脚本跑完一批请求后再跑第二批时报RuntimeError: Event loop is closed。原因asyncio.run()每次调用都会创建新的事件循环并在结束时关闭如果ClientSession在循环外创建第二次使用时循环已经关了。解决把ClientSession的创建和关闭都放在async函数内部用async with管理生命周期。或者整个脚本只调用一次asyncio.run()把所有任务放在同一个main函数里。5.5 Robots 协议与访问频率现象爬虫跑了一段时间后 IP 被封锁或者收到站点的警告邮件。原因没有遵守robots.txt的抓取规则或者请求频率远超正常用户行为。解决抓取前先访问https://目标站/robots.txt确认Disallow的路径不要碰。控制并发数单机爬虫建议不超过 10 个并发请求间隔至少 1 秒。框架里提到的 Robots 协议是爬虫的基本规矩不是技术限制而是行为边界越过这条线带来的麻烦远大于省下的那点时间。6. 从脚本到工程Scrapy 的落地姿势与一个验证习惯当你用 requests BeautifulSoup 写了十几个针对不同站点的脚本之后会发现大量代码在重复请求重试、去重、并发控制、数据管道。这时候就该上 Scrapy 了。框架里把 Scrapy 定位为「最灵活的定制化爬取」这个评价很准确——它的学习曲线比 requests 陡但一旦跑通扩展性和可维护性是脚本比不了的。Scrapy 的核心概念是 Spider、Item、Pipeline、Middleware。Spider 定义抓取逻辑Item 定义数据结构Pipeline 负责清洗和存储Middleware 处理请求和响应的拦截。一个最小可运行的 Spiderimport scrapy class ProductSpider(scrapy.Spider): name product start_urls [https://example.com/products?page1] custom_settings { DOWNLOAD_DELAY: 1.5, # 请求间隔秒 CONCURRENT_REQUESTS: 8, # 并发数 RETRY_TIMES: 3, # 重试次数 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, } def parse(self, response): for item in response.css(.product-list .item): yield { title: item.css(.title::text).get(default).strip(), price: item.attrib.get(data-price, ), link: response.urljoin(item.css(a::attr(href)).get()), } # 翻页找到下一页链接继续抓 next_page response.css(a.next::attr(href)).get() if next_page: yield response.follow(next_page, callbackself.parse)custom_settings里的DOWNLOAD_DELAY和CONCURRENT_REQUESTS是最需要调的两个参数。DOWNLOAD_DELAY1.5表示每个请求之间等 1.5 秒配合CONCURRENT_REQUESTS8实际对目标站点的压力大约是每秒 5 个请求。如果目标站点响应慢或者有限流迹象把并发降到 4、延迟加到 3 秒。RETRY_TIMES3让失败的请求自动重试省去手动写重试逻辑。response.follow会自动处理相对 URL 和域名过滤比手动拼urljoin再yield Request简洁。callbackself.parse表示下一页返回的数据仍然用同一个解析函数处理形成递归抓取。Scrapy 跑起来之后验证数据质量有个习惯我一直在用先跑-s CLOSESPIDER_ITEMCOUNT20限制只抓 20 条把输出导成 JSON 检查字段完整性和去重情况确认没问题再放开全量抓取。scrapy crawl product -s CLOSESPIDER_ITEMCOUNT20 -o sample.jsonCLOSESPIDER_ITEMCOUNT20表示抓到 20 条就自动停止-o sample.json把结果输出到文件。打开sample.json检查标题有没有空值、价格格式是否统一、链接是否完整。这个习惯帮我省过很多次「跑了几万条才发现字段全错」的后悔药。从那以后我每次写新 Spider都强制先跑 20 条样本验证再上全量。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表