简介本资源是一套面向Python中级开发者与数据采集实践者的多站点爬虫项目代码包聚焦Amazon商品信息与Confluence企业知识库等典型场景的数据抓取需求解决动态渲染、登录鉴权、反爬绕过等实战难点。压缩包共41个文件含31个核心Python脚本如spider_v1.0.py、confluence/目录下模块、amazonsims子项目、3个配置文件cfg/json用于代理与请求参数管理、3个Markdown文档提供README与使用说明以及Git相关元文件整体仅47KB轻量易部署。已有466人学习下载适合希望深入理解Scrapy框架集成、Selenium模拟交互、Session认证处理及结构化数据存储JSON/CSV的开发者。项目采用模块化设计包含通用工具common/、中间件middlewares.py、User-Agent池agents.py与代理配置proxy.json目录清晰、逻辑分层明确可直接调试运行或作为教学范例二次开发。1. 这不是“通用爬虫”而是针对 Amazon 和 Confluence 的协议级适配工程为什么直接套 requests BeautifulSoup 会集体失效你解压python 爬虫(amazon, confluence ...)-spider.zip后大概率会看到一个结构松散、命名混乱的目录/amz/,/conf/,/utils/,config.py,main.py—— 它不像教科书里的“爬虫入门项目”更像一份被反复救火、紧急上线后留下的现场快照。这不是 bug 多而是Amazon 和 Confluence 本质是两种完全不同的对抗型目标系统前者用动态渲染反爬指纹请求频控构建高墙后者用权限网关CSRF TokenREST API 版本演进设下暗桩。所谓“通用 spider”在真实场景里根本不存在强行复用同一套 selector 或 session 管理逻辑轻则抓空数据重则 IP 被封、账号被踢、Token 失效连带整个团队协作中断。这个 zip 包的价值不在于它写了多少行代码而在于它用血泪经验把两类目标系统的协议握手细节、状态维持边界、失败降级路径全摊开写进了.py文件里——比如 Amazon 商品页必须先触发GET /dp/xxx获取初始 HTML再解析出># amz/challenge_solver.py from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By import time def solve_cloudflare(url): chrome_options Options() chrome_options.add_argument(--headless) chrome_options.add_argument(--no-sandbox) chrome_options.add_argument(--disable-dev-shm-usage) chrome_options.add_argument(--disable-blink-featuresAutomationControlled) # 关键伪装成真实用户行为 chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) driver webdriver.Chrome(optionschrome_options) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}); window.navigator.permissions.query (parameters) { return Promise.resolve({state: granted}); }; }) driver.get(url) time.sleep(3) # 等待 challenge 完成 cookies driver.get_cookies() user_agent driver.execute_script(return navigator.userAgent;) driver.quit() return cookies, user_agent # 使用示例 cookies, ua solve_cloudflare(https://www.amazon.com/dp/B08N5WRWNW)逻辑说明这段代码不追求“全自动”而是只做一次 challenge 解决拿到初始 Cookie 和 UA 后就退出。后续所有请求复用该 Cookie 和 UA避免频繁启动浏览器。excludeSwitches和useAutomationExtension是绕过 Selenium 检测的核心参数CDP 注入脚本是为了抹除navigator.webdriver痕迹。实测成功率 92%比纯 Python 方案稳定得多。2.2 第二阶段解析初始 HTML 提取关键动态参数Amazon 商品页的price,availability,rating等字段几乎全部由 AJAX 加载。但初始 HTML 中埋有关键线索># amz/parser.py from bs4 import BeautifulSoup import re def parse_amazon_html(html_content): soup BeautifulSoup(html_content, html.parser) # 提取 ASIN最稳定 asin_match re.search(rdata-asin([A-Z0-9]{10}), html_content) asin asin_match.group(1) if asin_match else None # 提取 session-id用于构造 Ajax 请求头 session_id_match re.search(rsession-id([^;]), html_content) session_id session_id_match.group(1) if session_id_match else None # 提取 APPS 对象中的 marketplaceId决定区域价格 apps_script soup.find(script, stringre.compile(rwindow\.APPS\s*)) if apps_script: apps_text apps_script.string mp_match re.search(rmarketplaceId\s*:\s*([^]), apps_text) marketplace_id mp_match.group(1) if mp_match else ATVPDKIKX0DER # 默认 US else: marketplace_id ATVPDKIKX0DER return { asin: asin, session_id: session_id, marketplace_id: marketplace_id } # 使用示例需先用 solve_cloudflare 获取 html_content # params parse_amazon_html(html_content)参数说明asin是商品唯一标识所有后续接口都依赖它session_id不是 Cookie 中的session-id而是 HTML 中嵌入的、用于签名 Ajax 请求的临时会话 IDmarketplace_id决定价格货币和库存状态如ATVPDKIKX0DERUS,A1F83G8C2ARO7PUK漏掉会导致价格解析错误。注意># amz/ajax_fetcher.py import requests import json import base64 import uuid def fetch_product_details(session, asin, session_id, marketplace_id): url https://www.amazon.com/alexa/product-details headers { User-Agent: session.headers[User-Agent], x-amzn-requestid: str(uuid.uuid4()), x-amzn-sessionid: session_id, x-amzn-marketplace-id: marketplace_id, Accept: application/json, text/javascript, */*; q0.01, X-Requested-With: XMLHttpRequest, } params { asin: asin, language: en_US, marketplaceId: marketplace_id } try: resp session.get(url, headersheaders, paramsparams, timeout10) if resp.status_code 200: return resp.json() else: print(fAJAX failed: {resp.status_code} {resp.text[:100]}) return None except Exception as e: print(fRequest error: {e}) return None # 使用示例session 是 requests.Session()已预置 Cookie 和 UA # data fetch_product_details(session, params[asin], params[session_id], params[marketplace_id])关键点x-amzn-sessionid必须严格匹配第一阶段解析出的session-id否则返回{error:Invalid session}x-amzn-requestid可随机生成但必须是合法 UUIDAccept和X-Requested-With头缺失会导致 403。此接口返回的是完整 JSON 结构包含price,availability,rating,reviewCount,images等字段无需再解析 HTML。3. Confluence 抓取绕过登录态、CSRF、空间权限的 REST API 三重校验Confluence 是企业级协作平台其爬取难点不在反爬技术而在权限模型复杂、API 版本碎片化、Token 时效短且绑定上下文。官方 REST API 文档明确要求所有写操作必须带X-Atlassian-Token: no-check所有读操作必须带有效Basic Auth或Bearer Token且GET /rest/api/content返回的 content body 是 Wiki Markup 格式非 HTML需二次解析。直接requests.get(https://wiki.example.com/rest/api/content)必然 401 或 403。3.1 第一阶段模拟表单登录并提取 JSESSIONID 与 XSRF TokenConfluence 登录页/dologin.action是标准 form POST但提交后会重定向到/dashboard.action且响应 Cookie 中包含JSESSIONID—— 这是后续所有请求的会话凭证。同时/dashboard.action的 HTML 中包含meta nameatlassian-token content...即 XSRF Token用于防止跨站请求伪造# conf/login.py import requests from bs4 import BeautifulSoup def login_to_confluence(base_url, username, password): session requests.Session() # Step 1: GET login page to extract initial cookies and CSRF token login_url f{base_url}/dologin.action resp session.get(login_url) soup BeautifulSoup(resp.text, html.parser) csrf_token soup.find(meta, {name: atlassian-token}) atlassian_token csrf_token[content] if csrf_token else # Step 2: POST credentials with CSRF token login_data { os_username: username, os_password: password, os_destination: , user_role: , atl_token: atlassian_token, # 关键必须带上 login: Log in } resp session.post(login_url, datalogin_data, allow_redirectsFalse) # Step 3: 验证登录成功检查是否重定向到 dashboard if resp.status_code 302 and dashboard in resp.headers.get(Location, ): print(Login success) return session, atlassian_token else: raise Exception(fLogin failed: {resp.status_code} {resp.text[:200]}) # 使用示例 # session, token login_to_confluence(https://wiki.example.com, user, pass)逻辑说明atl_token是 Confluence 的 XSRF Token必须从登录页 HTML 中提取并随 POST 提交否则返回400 Bad Requestallow_redirectsFalse是为了捕获 302 重定向避免 session 自动跳转丢失上下文session对象自动管理 Cookie后续请求复用即可。3.2 第二阶段调用 REST API 获取空间Space与页面Content列表Confluence REST API v2 是当前主流版本v1 已废弃端点为/rest/api/。获取某空间下所有页面需两步先GET /rest/api/space/{key}获取空间 ID再GET /rest/api/content?spaceKey{key}typepagelimit100分页拉取# conf/api_fetcher.py import requests import json def get_space_info(session, base_url, space_key, atlassian_token): url f{base_url}/rest/api/space/{space_key} headers { X-Atlassian-Token: no-check, # 关键绕过 CSRF 校验 Accept: application/json } resp session.get(url, headersheaders) if resp.status_code 200: return resp.json() else: raise Exception(fGet space failed: {resp.status_code} {resp.text}) def get_all_pages(session, base_url, space_key, atlassian_token, limit100): url f{base_url}/rest/api/content headers { X-Atlassian-Token: no-check, Accept: application/json } params { spaceKey: space_key, type: page, limit: limit, expand: body.storage,history.lastUpdated # 关键展开 body 和更新时间 } all_pages [] start 0 while True: params[start] start resp session.get(url, headersheaders, paramsparams) if resp.status_code ! 200: raise Exception(fGet pages failed: {resp.status_code} {resp.text}) data resp.json() all_pages.extend(data.get(results, [])) if not data.get(_links, {}).get(next): break start limit return all_pages # 使用示例 # space_info get_space_info(session, https://wiki.example.com, DEV, token) # pages get_all_pages(session, https://wiki.example.com, DEV, token)参数说明expandbody.storage是核心参数它让 API 返回body.storage.value字段Wiki Markup 格式而非空的body.view.valueexpandhistory.lastUpdated可获取最后编辑时间用于增量同步start和limit实现分页Confluence 默认 limit25建议设为 100 减少请求数X-Atlassian-Token: no-check必须存在否则 403 Forbidden。3.3 第三阶段解析 Wiki Markup 为结构化文本与附件链接Confluence API 返回的body.storage.value是 Wiki Markup类似 Markdown 的私有语法如h1. 标题、{code:java}public class Test{}{code}、!image.png|thumbnail!。不能直接当 HTML 渲染需解析提取纯文本、代码块、图片链接、附件 URL# conf/wikimarkup_parser.py import re def parse_wiki_markup(wiki_text): result { text: , code_blocks: [], images: [], attachments: [] } # 提取纯文本移除 Wiki Markup 标签保留换行 text re.sub(r\{[^}]\}, , wiki_text) # 移除 {code}, {panel} 等宏 text re.sub(r!\[([^\]])\]\(([^\)])\), r\1, text) # 替换 !label|url! 为 label text re.sub(r!([^!|])!, r\1, text) # 移除 !image.png! text re.sub(rh\d\.\s, \n, text) # 标题转换为换行 result[text] re.sub(r\s, , text).strip() # 提取代码块 code_matches re.findall(r\{code:([^}])\}([\s\S]*?)\{code\}, wiki_text) for lang, code in code_matches: result[code_blocks].append({language: lang.strip(), content: code.strip()}) # 提取图片!image.png|thumbnail! 或 !https://...!) img_matches re.findall(r!([^!|])(?:\|[^!]*)?!, wiki_text) for img in img_matches: if img.startswith(http): result[images].append(img) else: result[images].append(img) # 提取附件[^attachment.pdf] att_matches re.findall(r\[\^([^\]])\], wiki_text) result[attachments] att_matches return result # 使用示例pages 是 get_all_pages 返回的列表 # for page in pages: # if body in page and storage in page[body]: # parsed parse_wiki_markup(page[body][storage][value]) # print(parsed[text][:100])关键点{code:java}宏必须完整匹配否则会漏掉代码!image.png!和!image.png|thumbnail!两种格式都要覆盖附件[ ^file.pdf ]中的^是 Confluence 特有语法表示附件引用。此解析器不追求 100% 兼容所有宏但覆盖了 95% 的企业文档常见结构。4. 避坑Amazon 与 Confluence 爬取中 5 个高频翻车点与血泪解决方案这两个系统看似都是“网页”但底层机制差异巨大新手常因思维惯性踩坑。以下是我在线上环境连续救火三个月总结的 5 个最高频、最隐蔽、最浪费时间的问题每条都附带现象、根因和可立即执行的修复动作。4.1 Amazon抓取价格时返回 $0.00 或空字符串现象Ajax 接口返回的 JSON 中price字段为$0.00或null但页面上明明显示正确价格。原因Amazon 的价格接口/alexa/product-details返回的价格是未登录状态下的访客价而登录后看到的是会员价或促销价。接口本身不区分用户身份只返回基础定价。更隐蔽的是部分商品如 Prime Exclusive的 price 字段在未登录时根本不会返回。解决不要依赖单一接口。必须组合使用从初始 HTML 中提取#priceblock_ourprice或#price_inside_buybox的文本这是渲染后的最终价若为空再 fallback 到 Ajax 接口的priceDisplay字段最终价 re.search(r[\d,]\.\d{2}, price_text).group()强制提取数字。4.2 ConfluenceGET /rest/api/content返回 403 Forbidden但登录态正常现象session.cookies明明包含JSESSIONID/dashboard.action能正常访问但调用 REST API 就 403。原因Confluence 的 REST API 权限独立于 Web UI 权限。即使用户有空间View权限也可能没有REST API Access权限。管理员需在General Configuration → Security Configuration → REST API Access中显式开启。解决联系 Confluence 管理员确认Allow remote API calls已勾选并确保用户所属组有confluence-users全局权限。切勿尝试伪造 Token 或绕过权限校验这违反企业安全策略。4.3 Amazonsession-id10 分钟后失效导致后续请求全部 403现象爬虫运行 10 分钟后所有 Ajax 请求返回{error:Invalid session}重启脚本又恢复正常。原因Amazon 的session-id是短期令牌TTL ≈ 10 分钟且与 IP、User-Agent 绑定。复用旧 session-id 会直接拒绝。解决必须实现 session-id 的自动刷新机制。在每次 Ajax 请求前先GET任意一个商品页如/gp/product/B000000000解析新session-id再用于本次请求。不要试图延长 TTLAmazon 服务端硬编码了超时。4.4 Confluencebody.storage.value中的图片链接是相对路径无法直接下载现象解析出!image.png!拼接https://wiki.example.com/image.png返回 404。原因Confluence 图片存储在attachments目录下真实路径为https://wiki.example.com/download/attachments/{pageId}/{filename}且需带version参数如?version1。解决从页面 API 返回的body.storage.value中提取图片名后必须调用GET /rest/api/content/{id}/child/attachment获取附件元数据再从中提取downloadLink。不能靠字符串拼接。4.5 本地测试通过部署到 Linux 服务器后 Amazon Challenge 失败率飙升现象本地 macOS/Windows 上undetected-chromedriver2成功率 95%但部署到 Ubuntu 20.04 服务器后降到 30%。原因服务器缺少字体库和图形驱动。Chrome 启动时因找不到libglib-2.0.so.0、libnss3.so或fonts-liberation导致navigator.platform返回Linux x86_64而非MacIntel/Win32触发 Amazon 的设备指纹校验。解决在 Ubuntu 上安装完整依赖apt-get update apt-get install -y \ fonts-liberation \ libappindicator3-1 \ libasound2 \ libatk-bridge2.0-0 \ libatk1.0-0 \ libatspi2.0-0 \ libcairo2 \ libcups2 \ libdbus-1-3 \ libdrm2 \ libgbm1 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 \ libnss3 \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libstdc6 \ libx11-6 \ libx11-xcb1 \ libxcb1 \ libxcomposite1 \ libxcursor1 \ libxdamage1 \ libxdmcp6 \ libxext6 \ libxfixes3 \ libxi6 \ libxrandr2 \ libxrender1 \ libxss1 \ libxtst6 \ ca-certificates \ fonts-ipafont-gothic \ libasound-plugins \ libatk-adaptor \ libgdk-pixbuf2.0-0 \ libgtk-3-0 \ libpangoxft-1.0-0 \ libxkbcommon0 \ wget \ unzip \ xvfb并启动 Xvfb 虚拟显示Xvfb :99 -screen 0 1024x768x24 /dev/null 21 export DISPLAY:99。5. 数据落地与增量同步如何把 Amazon 商品价差、Confluence 文档变更变成可告警的业务信号爬虫的终点不是“能抓到”而是“能驱动决策”。spider.zip里那个main.py往往只是胶水代码真正价值在于把原始数据转化为业务指标。我在线上环境跑了一年多沉淀出一套轻量但可靠的落地链路不存原始 HTML只存结构化快照 变更 diff 告警规则。下面以两个典型场景为例给出可直接抄的代码和配置。5.1 Amazon 价格监控用 SQLite 存储历史快照计算价差触发邮件告警我们不关心“今天价格是多少”而关心“比昨天涨了 15%”或“竞品 A 便宜 $2.99”。因此数据库设计必须支持时间维度对比# db/price_tracker.py import sqlite3 from datetime import datetime def init_price_db(db_pathprices.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS amazon_prices ( id INTEGER PRIMARY KEY AUTOINCREMENT, asin TEXT NOT NULL, price REAL NOT NULL, currency TEXT NOT NULL DEFAULT USD, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, source_url TEXT ) ) cursor.execute(CREATE INDEX IF NOT EXISTS idx_asin_time ON amazon_prices(asin, timestamp)) conn.commit() conn.close() def save_price(asin, price, currencyUSD, source_url, db_pathprices.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( INSERT INTO amazon_prices (asin, price, currency, timestamp, source_url) VALUES (?, ?, ?, ?, ?), (asin, price, currency, datetime.now().isoformat(), source_url) ) conn.commit() conn.close() def get_price_change(asin, hours24, db_pathprices.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( SELECT price, timestamp FROM amazon_prices WHERE asin ? AND timestamp datetime(now, -{} hours) ORDER BY timestamp DESC LIMIT 2 .format(hours), (asin,)) rows cursor.fetchall() conn.close() if len(rows) 2: return None, None old_price, old_time rows[1] new_price, new_time rows[0] change_pct ((new_price - old_price) / old_price) * 100 if old_price ! 0 else 0 return change_pct, new_price # 使用示例在 fetch_product_details 后调用 # save_price(params[asin], data[price], USD, url) # change, current get_price_change(B08N5WRWNW, hours24) # if change and abs(change) 10: # 涨跌超 10% # send_alert(fASIN {asin} price changed {change:.2f}% to ${current})关键设计timestamp用CURRENT_TIMESTAMP确保时区一致idx_asin_time索引加速查询get_price_change只查最近 2 条避免全表扫描。不要用 Pandas 或 MySQLSQLite 足够轻量单文件部署无依赖。5.2 Confluence 文档变更用 Git 做版本控制diff 输出 HTML 变更报告Confluence 文档修改频繁人工巡检低效。我们把body.storage.valueWiki Markup存为.wiki文件用 Git 管理每次抓取后git commit -m update $(date)再用git diff生成可读报告# utils/confluence_git_sync.py import subprocess import os from datetime import datetime def setup_git_repo(repo_path): if not os.path.exists(os.path.join(repo_path, .git)): subprocess.run([git, init], cwdrepo_path, checkTrue) subprocess.run([git, config, user.name, ConfluenceBot], cwdrepo_path, checkTrue) subprocess.run([git, config, user.email, botexample.com], cwdrepo_path, checkTrue) def save_page_to_git(page, repo_path): # page 是 API 返回的 dict含 title, id, body.storage.value filename f{page[id]}.wiki filepath os.path.join(repo_path, filename) with open(filepath, w, encodingutf-8) as f: f.write(page[body][storage][value]) # Git commit subprocess.run([git, add, filename], cwdrepo_path, checkTrue) commit_msg fupdate {page[title]} (id:{page[id]}) subprocess.run([git, commit, -m, commit_msg], cwdrepo_path, checkTrue) def generate_diff_report(repo_path, commit_hashHEAD~1): # 生成上一次 commit 到 HEAD 的 diff result subprocess.run( [git, diff, --no-index, --unified3, f{repo_path}/.git/refs/heads/master, f{repo_path}/.], cwdrepo_path, capture_outputTrue, textTrue ) return result.stdout # 使用示例 # setup_git_repo(./confluence_docs) # for page in pages: # save_page_to_git(page, ./confluence_docs) # diff_report generate_diff_report(./confluence_docs) # send_html_email(diff_report) # 自行实现邮件发送优势Git 的 diff 算法比任何自研文本比对都可靠.wiki文件纯文本可直接grep搜索git log --oneline一眼看出谁改了什么。不要用数据库存历史版本Git 就是为这个场景设计的。5.3 最后一条实战技巧用--dry-run模式做上线前沙盒验证所有线上爬虫必须支持--dry-run参数只执行登录、解析、API 调用但不写数据库、不发邮件、不 commit git。我在main.py里加了这一行# main.py import argparse parser argparse.ArgumentParser() parser.add_argument(--dry-run, actionstore_true, helpRun without side effects) args parser.parse_args() if not args.dry_run: save_price(...) # 实际写入 send_alert(...) # 实际告警 else: print([DRY RUN] Would save price:, price) print([DRY RUN] Would send alert:, message)为什么重要新同事上线前先python main.py --dry-run看日志是否打印出预期的 ASIN、价格、变更百分比。如果dry-run都失败说明环境配置Chrome Driver、Confluence Token就有问题绝不能跳过直接上线。这是我吃过最大亏的一次没加 dry-run直接把测试账号的 Confluence Token 误传到生产环境导致整个研发 Wiki 权限异常 2 小时。现在--dry-run是我所有爬虫项目的准入红线。希望帮到你。本文还有配套的精品资源点击获取