
简介对于正在系统学习Python网络爬虫的初学者与自学者而言这份习题答案是检验章节掌握程度、弥补知识盲区的实用配套资料。资源共47个文件、约3.4MB以py源码、txt答案解析和pyc编译文件为主另含png/jpg产品体系图及cfg、csv等配置数据文件覆盖了从基础语法到爬虫框架的常见练习场景。目前已吸引2865人学习使用说明其内容对多数读者具有直接参考价值。按第1章至第7章的顺序组织既有选择题答案也有操作题完整代码例如TCP/UDP客户端、爬取人民日报首页信息、调用豆瓣接口以及将数据存入MongoDB等同时穿插泰迪科技的大数据、商务数据分析和人工智能产品体系图便于将爬虫技术与企业真实业务场景建立联系。整体而言这份资料特别适合课后复习、考试备考或项目实战前的代码思路参考。1. 一份“Python网络爬虫技术_习题答案.rar”里真正该学的东西“习题答案.rar”这个名字第一眼会让人以为又是个凑页数的资源包但没有源码、没有项目正文真正值得拆开的反而是“Python网络爬虫技术”这七个字背后的考点地图。几乎所有入门教材的习题都绕不开同一条主线发请求、拿响应、解析页面、清洗数据、落库存储再加上反爬对抗、请求频率控制、异常重试这条暗线。把这份地图上的每个节点吃透比背答案本身有用得多。这篇文章从“爬虫的核心技术栈”讲起一路把 requests 请求、解析方案、数据清洗、SQLite 存储、协程并发和断点排错全部落到可复现代码上。适合正在刷 Python 爬虫习题的学生也适合转岗过来想系统梳理一遍的初级工程师。新手能照着跑通有几年经验的人看看参数边界和踩坑点不亏。2. 爬虫原理拆解习题答案覆盖的技术栈全貌2.1 从一次 HTTP 请求到数据落库爬虫到底在做什么爬虫不是一个库而是一套链路。无论题目问的是“requests 和 urllib 哪个好用”还是“BeautifulSoup 和 XPath 怎么选”本质都在考察这套链路里某个环节的取舍。最基础的链路可以拆成五步构造请求、发送请求、解析响应、清洗数据、持久化存储。任何一份习题答案里出现频率最高的三个模块无非是requests、BeautifulSoup4或lxml、pandas。有的进阶题目会引入scrapy或者aiohttp那是把同一套链路搬到并发场景里。先把这个链路搞明白后面所有题目都能找到对应位置而不是死记某个函数的参数。另一个容易被忽略的点是“反爬”这个环节。习题里常见的 User-Agent 伪装、Referer 校验、Cookie 传递、请求频率控制本质上都是在模拟真实浏览器行为。理解 HTTP 协议的状态码语义200 表示成功403 表示服务器拒绝503 表示限流比抄代码更重要因为几乎所有爬虫排错都始于“先看返回了什么状态码”。2.2 请求库选型requests 为什么是习题标准答案Python 里发 HTTP 请求的方案有三个层级标准库 urllib、第三方库 requests、异步库 aiohttp。习题答案几乎清一色选 requests不是因为它性能最好而是因为它把底层连接管理、编码推断、Cookie 处理这些脏活全部封装掉了代码量最少出错的概率也最低。urllib 最大的痛点是编码和 header 处理太原始。比如urllib.request.urlopen()拿到的响应对象要手动做.read().decode(utf-8)一旦服务器返回 gzip 压缩内容还要自己去解压。 requests 一句response.text就自动完成了解码和编码推断。当习题要求“抓取网页并输出标题”时requests 的答案只有三行import requests resp requests.get(https://httpbin.org/html, timeout10) resp.encoding resp.apparent_encoding # 根据响应字节推断编码 print(resp.text[:500]) # 打印前 500 字符确认拿到内容这段代码里timeout10一定要养成习惯。不设超时时如果目标服务器 TCP 连接一直不响应程序会挂死在那里这在批量爬取场景下是致命的。resp.apparent_encoding是从响应字节里做编码嗅探处理中文页面时可以有效避免“乱码回传”的经典坑。2.3 解析方案左中右正则、BeautifulSoup 与 XPath 的适用边界拿到 HTML 之后怎么把需要的数据抠出来是习题里区分度最高的一类题目。三种主流方案各有边界选错方案会在后续题目里连环踩坑。正则表达式适合提取出现在script标签里、或作为 JSON 字符串嵌入页面的数据。它的优势是零依赖、速度快致命弱点是无法处理嵌套 HTML 结构。比如提取所有超链接时用正则a href(.*?)遇到换行或属性顺序不同就会漏掉。BeautifulSoup 用起来最像人话find_all、select_one等 API 对新手极其友好代价是性能最差。解析一个几兆的 HTML 文档BeautifulSoup 会先把整个 DOM 树建起来耗时是 lxml 的好几倍。对习题来说体感不明显但如果题目后面追加一句“批量爬取 1000 个页面”性能差距就出来了。XPath 配合 lxml 是正式抓取时的常用选择定位表达式短、执行快还支持从 HTML 和 XML 两种源里抓取。选择题里如果问“哪个解析库性能最好”答案通常是 lxml如果问“哪个最易读”BeautifulSoup 优先。下面是同一个抓取目标用三种方式实现的对比目标是从豆瓣读书 Top250 抓取书名。实际做题时用 XPath 版本的路径可以直接复制浏览器开发者工具里的完整路径但注意那个路径经常带tbody节点复制后要手动清理。# 方案一正则 import re import requests resp requests.get(https://book.douban.com/top250, timeout10) title_list re.findall(rtd headerstitle.*?a.*?(.*?)/a, resp.text, re.S) print(title_list[:5])# 方案二BeautifulSoup from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) titles [a.get_text(stripTrue) for a in soup.select(td[headerstitle] a)] print(titles[:5])# 方案三lxml XPath from lxml import etree tree etree.HTML(resp.text) titles tree.xpath(//tr[classitem]//td[headerstitle]/a/text()) print(titles[:5])re.S标志表示.可以匹配换行正则方案里漏掉这个参数是拿到空列表的最常见原因。BeautifulSoup 里html.parser是内置解析器不需要额外装包但它对畸形 HTML 的容错不如 lxml把html.parser换成lxml后容错性会好一截。XPath 表达式最依赖页面结构只要页面改版路径就会失效这也是它最大的维护成本。2.4 动态页面习题从“静态抓取”升级到“接口模拟”的分水岭基础题抓的是服务端渲染的 HTML数据都嵌在响应里。但现在的网站大量使用前后端分离架构列表数据由 JavaScript 异步加载直接 GET 首页 URL 拿不到任何业务数据。这时候习题答案里就会出现两种思路Selenium 渲染浏览器或者直接找接口。Selenium 的思路是模拟真实浏览器执行 JavaScript能拿到最终渲染结果代价是慢、费内存、还要额外下载 WebDriver 驱动文件。更高效的做法是开发者工具里切到 Network 面板刷新页面后看 XHR 类型的请求找到返回 JSON 的那个 URL直接用 requests 请求它import requests api_url https://api.example.com/v1/books?page1page_size20 resp requests.get(api_url, timeout10) data resp.json() # 直接把响应体解析为 Python 字典 print(data[data][list][0][title])resp.json()的前提是响应头里Content-Type是application/json如果服务器返回的是 JSON 字符串但 Content-Type 是text/htmlresp.json()也能解析只是要确保内容确实是合法 JSON。这个“先找接口再降级到 Selenium”的判断顺序是高级习题里很看重的工程意识。3. 练习题的经典九宫格把核心参数和数据链路一次吃透3.1 用 requests 组件一套最小可用爬虫框架翻开任何一份 Python 网络爬虫技术习题第一道编程题大概率是“抓取某个页面并提取指定信息”。它考察的不是单个函数而是三个基本功请求头伪装、异常处理、数据提取。这三个能力拼在一起就是一具可以反复套用的最小框架。下面的代码把这三块揉在一起直接可用于绝大多数静态页面。框架里的get_page()和extract_data()是两个独立函数可以根据不同习题替换里面的 URL 和解析逻辑import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def get_page(url): session requests.Session() retry Retry(total3, backoff_factor0.5, status_forcelist[500, 502, 503]) adapter HTTPAdapter(max_retriesretry) session.mount(http://, adapter) session.mount(https://, adapter) headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9, } try: resp session.get(url, headersheaders, timeout10) resp.raise_for_status() return resp.text except requests.RequestException as e: print(f请求失败: {e}) return None def extract_data(html): from lxml import etree if not html: return [] tree etree.HTML(html) return tree.xpath(//h2//text()) if __name__ __main__: page get_page(https://example.com) result extract_data(page) print(result)逻辑说明Retry(total3)表示最多重试 3 次backoff_factor0.5让每次重试等待时间按 0.5 秒的倍数递增第一次等 0.5 秒、第二次等 1 秒status_forcelist指当响应状态码为 500、502、503 时自动重发请求。这个机制用来处理服务器偶发抖动比自己在外面套time.sleep()更优雅。参数说明max_retries作用于连接池和Retry对象配合时能覆盖连接拒绝、超时、特定状态码三类情况。很多人只写了session.get()不挂HTTPAdapter遇到瞬时网络抖动就直接抛异常这在批量抓取的时候会让整套流程中断。这个框架的另一个刻意设计是headers里只放了User-Agent和Accept-Language实际做题时可以按目标网站的响应头继续补充Referer和Origin——大部分网站只校验这两个字段补上后 403 概率会显著下降。3.2 一张表记住爬虫的核心参数边界习题里经常出现“下面哪个参数能避免请求被拒绝”“timeout 能设吗”这类选择题。与其零散记忆不如把参数按请求阶段分组。以下是做题和实际开发都高频使用的参数速查表参数位置作用典型值踩坑点timeoutrequests.get()连接和读取的超时时间单位秒(3.05, 10)只传一个数字表示两阶段共用一个值分离设置更保险headers请求头携带 UA、Referer、Accept-Language 等User-Agent必带漏了 UA 最容易触发 403paramsURL 查询字符串把字典自动拼接到 URL 后{page: 1}有中文时 requests 会自动做 URL 编码data表单提交POST 请求的请求体{username: test}和json参数互斥不要同时传verifySSL 校验是否校验证书False关闭会有安全提示本地测试用生产不建议allow_redirects重定向是否自动跟随 301/302True设为 False 时可手动捕获重定向目标 URL每个参数的边界都对应一类真实报错。timeout不设 → 程序挂死headers不完整 → 403 Forbiddenverify为 False 但没处理警告 → 日志里刷满InsecureRequestWarningallow_redirects默认跟随意味着登录后返回的跳转页面会被直接吞掉。做题时如果某个参数没见过优先去翻 requests 官方文档的 API Reference比背答案可靠。3.3 session 与登录态带 Cookie 的请求为什么会写进习题答案很多习题会在第二问追加“保持登录状态后继续抓取”。这个场景必须用requests.Session()而不是每次调用requests.get()。Session 对象会自动保存服务端返回的 Cookie并在后续请求中自动携带相当于一个轻量级的浏览器会话。import requests session requests.Session() login_url https://example.com/api/login login_data {username: test, password: 123456} resp session.post(login_url, datalogin_data, timeout10) print(resp.status_code, resp.cookies.get_dict()) # 登录后携带 session 抓取个人中心数据 profile session.get(https://example.com/profile, timeout10) print(profile.text[:200])逻辑说明第一次 POST 登录后服务器在响应头里通过Set-Cookie下发会话标识Session 会把 Cookie 存在内部。第二次 GET 时 Session 自动把 Cookie 拼进请求头服务器据此识别身份。参数说明resp.cookies.get_dict()可以查看当前会话持有哪些 Cookie调试登录失败时重点检查这一步——如果输出为空说明服务器没有下发 Cookie登录大概率失败错误原因可能在前面的login_data字段名不对。有些习题的登录接口返回的是 JSON里面带 token需要先解析出来手动塞进 headerAuthorization: Bearer tokenSession 的自动 Cookie 机制管不到这种场景。遇到返回 JSON 的登录接口时在resp.json()里逐层找 token 字段再拼到headers里发下一次请求这是进阶习题的常见考察方向。4. 从解析到入库数据清洗和落盘的正确打开方式4.1 pandas 清洗从网页上抓下来的原始数据网页上抓下来的数据几乎不可能是干净的。常见的问题包括数字里混着逗号和百分号、日期格式五花八门、空值以--或暂无出现。习题的隐藏考点就在这里——爬取只是前半段把数据整理成可分析的结构化表格才决定最终分值。pandas 是处理这类工作的标准工具。它的DataFrame接受列表、字典、CSV 文件等多种来源配合rename、to_numeric、dt等 API一个页面抓下来的脏数据能在几行内完成清洗。下面是一个处理“价格列带符号、日期列格式混乱”的实际案例import pandas as pd raw_data [ {title: A, price: ¥1,299, date: 2024-03-15}, {title: B, price: ¥899, date: 2024/3/16}, {title: C, price: --, date: 2024.03.17}, ] df pd.DataFrame(raw_data) df[price] df[price].replace(--, pd.NA) df[price] ( df[price] .str.replace([^\d.], , regexTrue) # 删除货币符号和逗号 .astype(float) ) df[date] pd.to_datetime(df[date], formatmixed, dayfirstFalse) print(df.dtypes)replace(--, pd.NA)先把无效值转换成 pandas 的缺失值标记这样后续用dropna()或fillna()时才有统一入口。正则表达式[^\d.]的效果是保留数字和小数点删除¥、逗号等干扰字符。to_datetime里的formatmixed是 pandas 2.0 之后支持的特性可以自动识别同列中不同格式的日期字符串老版本需要分别清洗再合并这也是一个容易忽略的版本差异点。清洗完成后经常需要做类型再校验df.info()看每列的非空数量df.describe()看数值列的分布。如果price列里还有object类型说明上面的正则没有完全匹配回看原始字符串里是否有全角符号全角符号要用[\uFF00-\uFFEF]先转换。4.2 把清洗结果写进 SQLite习题里“存储”的标准答案数据清洗之后存储方案通常会出现在习题的最后一问。CSV 是输出到本地最粗暴直接的方式但结构化和查询能力都不足。SQLite 单文件部署、无需额外服务和驱动Python 标准库自带支持是习题答案里出现频率最高的存储选择。下面的代码演示了从 DataFrame 写入 SQLite 并完成一次查询的最小流程import pandas as pd import sqlite3 df pd.read_csv(clean_output.csv) # 假设上一步已保存为 CSV conn sqlite3.connect(books.db) df.to_sql(books, conn, if_existsreplace, indexFalse) query SELECT title, price FROM books WHERE price 500 ORDER BY price ASC LIMIT 5 result pd.read_sql_query(query, conn) print(result) conn.close()to_sql会把 DataFrame 的列名直接当作数据库字段名if_existsreplace表示表已存在时先删除再重建。indexFalse很关键否则 pandas 会默认把行号写成一列名为index的字段。read_sql_query返回 DataFrame后续可直接做可视化或统计不用再经过一次转换。这里有个实际的坑to_sql默认创建的表字段类型是FLOAT和TEXT如果之前清洗时把日期列转成了datetime64写入后日期会变成TEXT再通过 SQL 比较日期大小会出现字符串比较的问题。正确做法是在 SQL 里用date()函数包装字段或者插入前先df[date] df[date].astype(str)。4.3 合规判断与请求策略答案里不写但做题必须知道的边界习题只关心能不能爬到数据但实际部署时“能不能爬”才是隐藏前提。国内关于数据抓取的规则核心依据是网站可访问性声明和 robots.txt 约定以及《数据安全法》和《个人信息保护法》对个人信息收集的限制。做题和练习时只抓公开页面、只爬自己账户有权访问的数据、不绕过登录鉴权这是基本的边界。技术上合规爬虫的通用策略有三条线索控制频率、识别协议、尊重退出机制。time.sleep(random.uniform(1, 3))是简单直接的限速方式更标准的做法是 BaseSpider 里配置DOWNLOAD_DELAY或在 requests 循环里挂一个限速器。robots.txt 可以在请求前用urllib.robotparser先解析import urllib.robotparser rp urllib.robotparser.RobotFileParser() rp.set_url(https://example.com/robots.txt) rp.read() # 检查指定的 User-Agent 是否有权抓取路径 can_fetch rp.can_fetch(MyCrawlerBot, /product/123.html) print(allowed if can_fetch else blocked)can_fetch返回布尔值表示该 User-Agent 是否允许抓取对应路径。set_url指向的 robots.txt 路径必须和后续请求的域名完全一致否则会被当作不同站点的规则。另外注意 robots.txt 本身也可能不存在此时read()不会抛异常can_fetch默认返回 True。这个检查放在爬虫启动前、而不是每次请求前避免重复网络请求。5. 用协程把爬虫提速一个量级再验证习题答案的边界5.1 用 asyncio 和 aiohttp 改造 requests 同步循环当你把前四章的代码跑通习题也刷完了下一步自然是把“能跑”变成“跑得快”。requests 发的是同步阻塞请求一个页面至少等网络往返时间100 个页面要乘 100。aiohttp 配合asyncio可以把并发请求数量提升到几十路但注意它要求目标网站允许并发否则加重对方压力。下面这段代码是改造的最小模板并发数通过Semaphore控制import asyncio import aiohttp SEMAPHORE_LIMIT 8 # 控制最大并发连接数 async def fetch_one(session, url, semaphore): async with semaphore: try: async with session.get(url, timeout10) as resp: content await resp.text() return url, resp.status, len(content) except Exception as e: return url, 0, str(e) async def main(): urls [fhttps://example.com/page/{i} for i in range(1, 51)] semaphore asyncio.Semaphore(SEMAPHORE_LIMIT) async with aiohttp.ClientSession() as session: tasks [fetch_one(session, u, semaphore) for u in urls] results await asyncio.gather(*tasks) for url, status, length in results: print(url, status, length) asyncio.run(main())逻辑说明Semaphore让同一时间最多有 8 个请求在途防止请求过快触发目标服务器限流。asyncio.gather并发执行所有任务resp.text()是异步方法必须await才能拿到文本。参数说明SEMAPHORE_LIMIT的合理值取决于目标站点的带宽和已知的限流阈值。对本地测试的小型网站8 到 16 都是安全区间对公共 API请先看服务端返回的RateLimit-Limit等响应头来确定。还要注意aiohttp的timeout参数是ClientTimeout类型直接传数字会报错建议改成aiohttp.ClientTimeout(total10)。5.2 断点排错从报错信息反推习题考察点拿到错误信息先去定位层级而不是复制到搜索引擎。爬虫报错最常见的是三层连接层、解析层、类型层。连接层报错常见有ConnectionError目标服务器拒绝连接、SSLError证书校验失败、Timeout请求超时。解析层报错是XPath表达式返回空列表、find_all结果为空、KeyError。类型层报错发生在把字符串当数字做运算、把 None 当字符串拼接时。排错顺序是先看状态码再打印响应头最后逐段注释掉解析代码定位问题行。VSCode 里直接在行号左侧打断点用调试面板的 Watch 查看resp.text[:2000]和tree.xpath()的返回值比 print 一遍遍刷更高效。很多人卡住的原因是“只打印了resp.text但没打印headers”很多异常可以通过resp.headers.get(Server)和resp.headers.get(X-Powered-By)判断出对方是 Nginx 还是 Apache从而推断它常用的限流策略。5.3 自己写一份“习题答案校验脚本”习题答案里常出现“运行结果如下”的截图但代码在执着于某个特定环境才能跑通。与其盲目照抄不如准备一个校验脚本把题目要求的输出结果和实际运行结果做对比判定答案是否可靠。这个脚本本身也是一个爬虫工程能力的综合练习。import subprocess import sys def run_student_code(code_path, input_dataNone): result subprocess.run( [sys.executable, code_path], inputinput_data, capture_outputTrue, textTrue, timeout30 ) return result.stdout.strip(), result.stderr.strip() expected {title: Python网络爬虫技术, count: 120} actual, err run_student_code(exercise_answer.py) if err: print(run error:, err) elif actual expected: print(PASS - output matches expected) else: print(FAIL - output mismatch) print(expected:, expected) print(actual :, actual)subprocess.run把答案脚本放到独立进程运行避免它的全局变量污染校验脚本。capture_outputTrue同时收集标准输出和标准错误timeout30可以识别死循环类的代码。输出对比时注意忽略空格和换行的差异先strip()再比对否则一个尾随空格就会误判。把这个脚本跑通后把expected改成习题答案里给出的值把exercise_answer.py替换成自己写的爬虫代码就能自动化地完成答案验证。这种方式比肉眼对比截图可靠也是“用工程方法验证技术答案”的最后一环——你不再需要依赖某份解压密码而是自己建立了一条可持续检验的标准。本文还有配套的精品资源点击获取