PYTHON CRAWLING FRAMEWORK

Scrapy代理接入与完整爬虫

一个文件即可启动Scrapy:从商品列表进入详情页,继续分页,并由Feed Export输出JSON Lines。所有请求都通过123Proxy固定网关。

语言Python 3.10+框架Scrapy测试目标Books to Scrape输出books.jsonl
打开对应产品提取 查看测试目标
01
END-TO-END RESULT

列表、详情、分页与Feed导出

测试目标Books to Scrapehttps://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

不要把真实代理密码提交到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)代理。