Scrapy代理接入与完整爬虫
一个文件即可启动Scrapy:从商品列表进入详情页,继续分页,并由Feed Export输出JSON Lines。所有请求都通过123Proxy固定网关。
语言Python 3.10+框架Scrapy测试目标Books to Scrape输出books.jsonl
01
END-TO-END RESULT
列表、详情、分页与Feed导出
测试目标Books to Scrape
https://books.toscrape.com/
运行时Python 3.10+Scrapy
输出文件结构化数据books.jsonl
代码直接连接隧道代理固定网关 proxy.123proxy.cn:36923。主机与端口无需替换,出口路由由当前产品配置决定;你只需设置控制台生成的完整代理用户名和密码。
02
CREDENTIALS
只把代理账密放进环境变量
PROXY_USER必须是控制台中的代理用户名,不是网站登录手机号。地区和SESSION场景应复制控制台生成的完整路由用户名。
export PROXY_USER="替换为控制台代理用户名"
export PROXY_PASS="替换为控制台代理密码"
# 隧道代理固定网关:proxy.123proxy.cn:36923$env:PROXY_USER="替换为控制台代理用户名"
$env:PROXY_PASS="替换为控制台代理密码"
# 隧道代理固定网关:proxy.123proxy.cn:36923
不要把真实代理密码提交到Git仓库、镜像、日志或前端代码中。
03
COMPLETE PROGRAM
books_spider.py
以下代码包含完整入口和输出逻辑。安装注释、运行命令与依赖声明保留在文件顶部,复制为对应文件后即可执行。
# requirements:
# python -m pip install scrapy
# run:
# scrapy runspider books_spider.py
import os
from urllib.parse import quote
import scrapy
from scrapy.crawler import CrawlerProcess
PROXY_HOST = "proxy.123proxy.cn"
PROXY_PORT = 36923
PROXY_USER = os.environ["PROXY_USER"]
PROXY_PASS = os.environ["PROXY_PASS"]
proxy_url = "http://{}:{}@{}:{}".format(
quote(PROXY_USER, safe=""),
quote(PROXY_PASS, safe=""),
PROXY_HOST,
PROXY_PORT,
)
class BooksSpider(scrapy.Spider):
name = "books_with_123proxy"
allowed_domains = ["books.toscrape.com"]
start_urls = [
"https://books.toscrape.com/catalogue/page-1.html"
]
custom_settings = {
"CONCURRENT_REQUESTS": 8,
"CONCURRENT_REQUESTS_PER_DOMAIN": 4,
"DOWNLOAD_DELAY": 0.25,
"DOWNLOAD_TIMEOUT": 30,
"RETRY_TIMES": 3,
"RETRY_HTTP_CODES": [429, 500, 502, 503, 504],
"ROBOTSTXT_OBEY": True,
"USER_AGENT": "123Proxy-Developer-Example/1.0",
"FEEDS": {
"books.jsonl": {
"format": "jsonlines",
"encoding": "utf8",
"overwrite": True,
}
},
"LOG_LEVEL": "INFO",
}
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url,
callback=self.parse,
meta={"proxy": proxy_url},
)
def parse(self, response):
for book in response.css("article.product_pod"):
detail_url = response.urljoin(
book.css("h3 a::attr(href)").get()
)
yield scrapy.Request(
detail_url,
callback=self.parse_book,
meta={
"proxy": proxy_url,
"list_price": book.css(
"p.price_color::text"
).get(),
},
)
next_href = response.css("li.next a::attr(href)").get()
if next_href:
yield scrapy.Request(
response.urljoin(next_href),
callback=self.parse,
meta={"proxy": proxy_url},
)
def parse_book(self, response):
table = {
row.css("th::text").get(): row.css("td::text").get()
for row in response.css("table.table tr")
}
yield {
"title": response.css("div.product_main h1::text").get(),
"price": response.meta["list_price"],
"availability": response.css(
"div.product_main p.availability::text"
).getall()[-1].strip(),
"upc": table.get("UPC"),
"product_type": table.get("Product Type"),
"url": response.url,
}
if __name__ == "__main__":
process = CrawlerProcess()
process.crawl(BooksSpider)
process.start()
04
ENGINEERING CHECKS
案例没有省略的关键逻辑
01request.meta.proxy使用完整账密代理URL
02列表页、详情页和下一页请求都显式携带代理
03限制单域名并发并设置下载间隔
04详情页补充UPC、类型与库存字段
05
SWITCH PROXY PRODUCT
替换网关,不改采集逻辑
代理产品网关域名端口适合任务
隧道代理
proxy.123proxy.cn36923混合池 / 纯住宅池隧道住宅代理
residential.123proxy.cn33000国家地区与SESSION不限量动态住宅
unlimit.residential.123proxy.cn10253不限流量住宅任务两类静态代理
控制台分配的固定代理IP返回端口直连IP,无网关域名隧道住宅代理的国家与SESSION写在完整代理用户名中
网关固定为 residential.123proxy.cn:33000。例如使用美国SESSION时,直接把控制台生成的完整用户名作为 PROXY_USER,代码无需解析或重组。
06
PRIMARY REFERENCES
框架文档与测试目标
07
TROUBLESHOOTING
常见问题
为什么每个Request都写meta.proxy?
Scrapy的HttpProxyMiddleware读取Request.meta中的proxy值。显式传递能让列表、详情和分页请求保持一致代理策略。
并发线程套餐应该怎么设置CONCURRENT_REQUESTS?
该值代表Scrapy侧并发上限,不应高于套餐可用并发,并需同时观察目标域名限制和浏览器外的其他任务。
SOCKS5可以直接使用这个案例吗?
Scrapy内置HTTP11下载处理器的SOCKS支持有限。需要SOCKS时应使用支持SOCKS的下载处理器,或优先采用HTTP(S)代理。