PYTHON BROWSER AUTOMATION

Python Selenium代理采集

Selenium启动Chrome时加载一个临时Manifest V3扩展,由扩展设置不限量动态住宅网关并响应代理认证。案例使用显式等待采集3页内容,结束时自动清理浏览器和临时凭证文件。

语言Python 3.10+ / Chrome 108+框架Selenium 4 + Chromium测试目标Quotes to Scrape / JavaScript输出quotes-selenium.json
打开对应产品提取 查看测试目标
01
END-TO-END RESULT

使用认证扩展采集JavaScript渲染页面

测试目标Quotes to Scrape / JavaScripthttps://quotes.toscrape.com/js/ 运行时Python 3.10+ / Chrome 108+Selenium 4 + Chromium 输出文件结构化数据quotes-selenium.json

代码直接连接不限量动态住宅固定网关 unlimit.residential.123proxy.cn:10253。主机与端口无需替换,出口路由由当前产品配置决定;你只需设置控制台生成的完整代理用户名和密码。

02
CREDENTIALS

只把代理账密放进环境变量

PROXY_USER必须是控制台中的代理用户名,不是网站登录手机号。地区和SESSION场景应复制控制台生成的完整路由用户名。

export PROXY_USER="替换为控制台代理用户名"
export PROXY_PASS="替换为控制台代理密码"
# 不限量动态住宅固定网关:unlimit.residential.123proxy.cn:10253

不要把真实代理密码提交到Git仓库、镜像、日志或前端代码中。

03
COMPLETE PROGRAM

selenium_proxy.py

以下代码包含完整入口和输出逻辑。安装注释、运行命令与依赖声明保留在文件顶部,复制为对应文件后即可执行。

# requirements:
#   python -m pip install selenium
# run:
#   python selenium_proxy.py
import json
import os
import shutil
import tempfile
import time
from pathlib import Path
from urllib.parse import urljoin

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

PROXY_HOST = "unlimit.residential.123proxy.cn"
PROXY_PORT = 10253
PROXY_USER = os.environ["PROXY_USER"]
PROXY_PASS = os.environ["PROXY_PASS"]
START_URL = "https://quotes.toscrape.com/js/"
MAX_PAGES = 3


def create_proxy_extension():
    extension_dir = Path(tempfile.mkdtemp(prefix="123proxy-selenium-"))
    manifest = {
        "manifest_version": 3,
        "name": "123Proxy Selenium Auth",
        "version": "1.0.0",
        "minimum_chrome_version": "108",
        "permissions": [
            "proxy",
            "webRequest",
            "webRequestAuthProvider",
        ],
        "host_permissions": ["<all_urls>"],
        "background": {"service_worker": "service_worker.js"},
    }
    worker = """
chrome.proxy.settings.set({
  value: {
    mode: "fixed_servers",
    rules: {
      singleProxy: {
        scheme: "http",
        host: "__HOST__",
        port: __PORT__
      },
      bypassList: ["localhost", "127.0.0.1"]
    }
  },
  scope: "regular"
});

chrome.webRequest.onAuthRequired.addListener(
  (details, callback) => {
    callback({
      authCredentials: {
        username: __USER__,
        password: __PASS__
      }
    });
  },
  {urls: ["<all_urls>"]},
  ["asyncBlocking"]
);
"""
    worker = (
        worker.replace("__HOST__", PROXY_HOST)
        .replace("__PORT__", str(PROXY_PORT))
        .replace("__USER__", json.dumps(PROXY_USER))
        .replace("__PASS__", json.dumps(PROXY_PASS))
    )
    (extension_dir / "manifest.json").write_text(
        json.dumps(manifest),
        encoding="utf-8",
    )
    (extension_dir / "service_worker.js").write_text(
        worker,
        encoding="utf-8",
    )
    return extension_dir


def text(node, selector):
    return node.find_element(By.CSS_SELECTOR, selector).text.strip()


def main():
    extension_dir = create_proxy_extension()
    options = webdriver.ChromeOptions()
    options.add_argument("--window-size=1440,1000")
    options.add_argument("--lang=en-US")
    options.add_argument("--load-extension=" + str(extension_dir))
    if os.environ.get("HEADLESS") == "1":
        options.add_argument("--headless=new")

    driver = webdriver.Chrome(options=options)
    driver.set_page_load_timeout(45)
    wait = WebDriverWait(driver, 30)
    records = []

    try:
        time.sleep(1)
        url = START_URL
        for _ in range(MAX_PAGES):
            driver.get(url)
            wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "div.quote"))
            )

            for quote in driver.find_elements(By.CSS_SELECTOR, "div.quote"):
                records.append({
                    "text": text(quote, "span.text"),
                    "author": text(quote, "small.author"),
                    "tags": [
                        tag.text.strip()
                        for tag in quote.find_elements(By.CSS_SELECTOR, "a.tag")
                    ],
                    "source_url": driver.current_url,
                })

            next_links = driver.find_elements(By.CSS_SELECTOR, "li.next > a")
            if not next_links:
                break
            url = urljoin(driver.current_url, next_links[0].get_attribute("href"))

        Path("quotes-selenium.json").write_text(
            json.dumps(records, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        print(
            "saved "
            + str(len(records))
            + " records to quotes-selenium.json"
        )
    finally:
        driver.quit()
        shutil.rmtree(extension_dir, ignore_errors=True)


if __name__ == "__main__":
    main()
04
ENGINEERING CHECKS

案例没有省略的关键逻辑

01代理网关固定为unlimit.residential.123proxy.cn:10253
02Manifest V3使用webRequestAuthProvider响应代理认证
03凭证只写入临时扩展目录,程序结束后删除
04默认有界面运行,HEADLESS=1时使用新版Chrome无头模式
05显式等待目标元素并限制最大分页数量
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

常见问题

为什么Selenium不能直接像Requests一样传入代理账密?

Chrome启动参数可以指定代理服务器,但不会安全接收用户名与密码。案例使用临时Manifest V3扩展设置代理并处理认证挑战。

为什么默认不是无头模式?

带认证扩展的可见模式兼容性更容易验证。Chrome 108+可设置HEADLESS=1使用新版无头模式,部署前应在目标镜像中实际测试扩展加载。

如何切换地区?

不限量动态住宅的地区与3-30分钟轮转周期在控制台按套餐设置,不写入Selenium代码。修改后等待同步并重启浏览器连接。

一个浏览器会占用多少并发?

浏览器会并行加载HTML、脚本、样式、图片、字体和接口,一页通常会产生多条在途代理请求。应以网络面板与实际p95数据测量。