用 urllib.parse.urljoin 拼下级分类 url 时,需确保上级 url 以 / 结尾(如 "https://example.com/cat/"),否则会被当作文件名处理而丢失路径;原始 href 应先用 requests.utils.requote_uri 处理编码,且勿对绝对 url 再调用 urljoin。

怎么用 urllib.parse.urljoin 拼出下级分类 URL
直接拼字符串容易出错,特别是当上级 URL 末尾有没有 /、路径里含参数或锚点时。urljoin 能自动处理相对路径解析逻辑,比字符串 + 更可靠。
常见错误现象:urljoin("https://example.com/cat", "subcat") 得到 https://example.com/subcat(丢了 /cat/),因为 "cat" 不是以 / 结尾,被当成文件名处理了。
- 确保上级 URL 以
/结尾:用urljoin("https://example.com/cat/", "subcat/")→ 正确得到https://example.com/cat/subcat/ - 如果原始链接来自 HTML 的
href,先用requests.utils.requote_uri处理编码问题,再传给urljoin - 不要对已完整的绝对 URL 再套
urljoin,它会按规则“降级”路径,可能意外截断
怎么从列表页提取所有分类链接并去重
多级分类常藏在导航栏、侧边栏或分页区域,XPath/CSS 选中器写窄了会漏,写宽了又混入无关链接(比如登录、首页)。
使用场景:一级分类页源码里有 @#@#@#@#@#@#@#@#@#@0,但也有 @#@#@#@#@#@#@#@#@#@1 这类干扰项。
- 优先用属性过滤:
response.css('nav a[href^="/category/"]::attr(href)')比'a::attr(href)'精准得多 - 提取后立刻用
set()去重,但注意:带不同查询参数的 URL(如/cat?id=1和/cat?id=2)是不同链接,别盲目删 - 用
urllib.parse.urlparse校验协议和域名,排除站外链接(netloc != "example.com"就跳过)
怎么控制爬取深度避免无限递归
没设深度限制时,分类页可能互相引用(比如“男装”页里又出现“女装”链接),或者分页参数生成循环 URL(page=1 ←→ page=2 ←→ page=1)。
性能影响:深度无约束会导致请求爆炸,内存里缓存的 URL 集合越来越大,甚至触发网站反爬限流。
- 在 Request 对象里加
meta={'depth': 1},回调函数里判断response.meta.get('depth', 0) >= MAX_DEPTH就不再发起子请求 -
MAX_DEPTH建议设为 3~4:一级分类 → 二级类目 → 商品列表 → (可选)商品详情,再深大概率是冗余结构 - 别只靠 depth 控制,配合 URL 模式白名单(如只允许匹配
r'/category/[^/]+/?$'的路径)双重保险
为什么用 scrapy.Request 而不是 requests.get 处理下级请求
用 requests.get 在 Scrapy 里发请求,会绕过中间件(User-Agent 轮换、重试、下载延迟)、丢失 stats 统计、无法复用连接池,还可能破坏并发调度逻辑。
兼容性影响:Scrapy 的 DOWNLOAD_DELAY、AUTOTHROTTLE 对原生 requests 完全无效,容易被封 IP。
- 统一用
scrapy.Request(url, callback=self.parse_subcat, meta={'parent': 'shoes'}),把上下文信息带下去 - 如果要传 POST 数据或自定义 headers,用
scrapy.FormRequest或scrapy.Request(..., headers={...}),别切回 requests - 需要同步等待某个异步结果?不是加
time.sleep,而是用twisted.internet.defer.inlineCallbacks配合yield—— 但这属于进阶需求,多数情况不需要
最常被忽略的是:分类页返回 301/302 重定向时,scrapy.Request 默认跟随,而 requests.get 默认不跟(除非显式设 allow_redirects=True),行为不一致会导致线上跑着跑着就漏数据。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











