轮换住宅代理是为规模化设计的:每个请求都使用新 IP,或在长达 30 分钟的粘性会话中使用同一 IP。而对于另一类工作来说,这恰恰是错误的:全天保持账号登录、让结账或监控会话持续数小时、或者每次都从同一个地方管理店铺的卖家账号。对于这些场景,网站期望看到的是同一个稳定的访问者,而变化的 IP 看起来就像是被盗用的会话。
ISP 代理正是为此而生。在整个计费周期内,每个代理都会停留在你选择的城市和互联网服务商上。但好的代理只是配置的一半,另一半是确保会话的其他方面同样保持一致。本指南将介绍我们向客户推荐的配置方案,并附上可供你调整的代码。
何时需要 ISP 代理而非轮换代理
| 你的工作 | 应使用的方案 |
|---|---|
| 跨多个网站采集多个页面 | 轮换住宅代理 |
| 在 30 分钟内完成的多步骤流程 | 住宅粘性会话 |
| 需要持续数小时的会话 | ISP 代理 |
| 日复一日地保持自己账号的登录状态 | ISP 代理 |
| 查看网站在特定城市和服务商下的呈现效果 | ISP 代理 |
30 分钟是一条实用的分界线。Shifter 上的住宅粘性会话最长持续 30 分钟。任何超过这个时长的工作,或者第二天还要返回同一账号的工作,都应该使用 ISP 代理。
Shifter 的 ISP 代理如何运作
- 你分配的每个 IP 都拥有自己的用户名,同一套餐下的所有用户名共享一个密码。
- 每个代理都通过相同的主机和端口
isp.shifter.io:443连接,支持 HTTP(S) 或 SOCKS5。 - 分配每个 IP 时,你可以从 120 个国家中选择城市和互联网服务商(ASN)。
- 只要套餐处于活跃状态,每个代理就会保持该城市和服务商不变。
- 其背后的 IP 地址可能会随时间刷新,就像家庭宽带连接偶尔会从服务商那里获得新地址一样。城市和服务商保持不变。
- 带宽不限:套餐按 IP 数量计价。
倒数第二点对持久登录来说很重要。你的配置应当预期地址会偶尔变化,并依赖那些保持不变的要素:位置、服务商和浏览器配置文件。
配置方案:五条规则
1. 每个账号一个代理,绝不共享。 为每个账号分配专属的 ISP 代理用户名,并固定这种配对关系。两个账号共用一个 IP 看起来像是关联账号;一个账号使用多个 IP 看起来像是从不同地方被访问。
2. 将每个 IP 分配到账号实际所在的地方。 如果账号属于芝加哥的一家店铺,就把它的 IP 分配到芝加哥的服务商上。从正确的城市和住宅服务商登录,看起来就是账号的正常使用习惯。
3. 为每个账号保留持久的浏览器配置文件。 Cookie、本地存储和已保存的会话正是让你保持登录状态的关键。按账号将它们存储在独立的配置文件目录中,并每次重复使用。每次运行都使用全新的浏览器,意味着每次都要重新登录,而这恰恰会触发验证提示。
4. 让浏览器与代理保持一致。 时区和语言应与出口节点的位置匹配。最简单的方法是询问出口节点的位置,并据此设置浏览器。我们关于匹配代理地理位置、时区和语言设置的指南涵盖了完整的信号列表。
5. 开始前先检查出口节点。 在打开账号之前,确认代理所在的城市和服务商是否符合预期。如果不符,就停下来,而不是从错误的地方登录。
代码实现
下面的模块用 Python 和 Playwright 将这五条规则付诸实践。它会验证账号与代理的映射关系,通过 ip-info.com(我们的免费 IP 地理定位 API)检查出口节点,并打开一个绑定到该账号专属代理的持久浏览器配置文件,时区则取自出口节点本身。
from pathlib import Path
import requests
ISP_GATEWAY = "isp.shifter.io:443"
def validate_assignments(accounts):
"""One ISP proxy per account, and no proxy shared between accounts."""
problems, seen_accounts, owner = [], set(), {}
for a in accounts:
if a["account"] in seen_accounts:
problems.append(f"{a['account']}: listed twice")
seen_accounts.add(a["account"])
other = owner.setdefault(a["isp_username"], a["account"])
if other != a["account"]:
problems.append(f"{a['isp_username']}: shared by {other} and {a['account']}")
return problems
def check_exit(account, password, get=requests.get):
"""Ask the exit where it is, and compare with where this account belongs."""
proxy = f"http://{account['isp_username']}:{password}@{ISP_GATEWAY}"
info = get("http://ip-info.com/json", proxies={"http": proxy, "https": proxy}, timeout=20).json()
problems = []
if info.get("city") != account["city"]:
problems.append(f"city is {info.get('city')}, expected {account['city']}")
if info.get("asn") != account["asn"]:
problems.append(f"ASN is {info.get('asn')}, expected {account['asn']}")
return {"ip": info.get("ip"), "timezone": info.get("timezone"), "problems": problems}
def browser_options(account, password, exit_info, profile_root):
"""Persistent Chromium profile for one account, pinned to its own ISP proxy."""
return {
"user_data_dir": str(Path(profile_root) / account["account"]),
"proxy": {"server": f"http://{ISP_GATEWAY}",
"username": account["isp_username"], "password": password},
"timezone_id": exit_info["timezone"],
"locale": account["locale"],
"viewport": {"width": 1440, "height": 900},
}
def open_account(playwright, account, password, profile_root="profiles"):
"""Check the exit first; only then open the account's own browser profile."""
exit_info = check_exit(account, password)
if exit_info["problems"]:
raise RuntimeError(f"{account['account']}: " + "; ".join(exit_info["problems"]))
options = browser_options(account, password, exit_info, profile_root)
return playwright.chromium.launch_persistent_context(**options)
使用方式如下:
import os
from playwright.sync_api import sync_playwright
from isp_sessions import open_account, validate_assignments
ACCOUNTS = [
{"account": "store-us", "isp_username": "us-chicago-illinois-as7922-XXXXX",
"city": "Chicago", "asn": 7922, "locale": "en-US"},
{"account": "store-de", "isp_username": "de-berlin-berlin-as3320-XXXXX",
"city": "Berlin", "asn": 3320, "locale": "de-DE"},
]
password = os.environ["ISP_PASSWORD"]
problems = validate_assignments(ACCOUNTS)
if problems:
raise SystemExit("\n".join(problems))
with sync_playwright() as p:
browser = open_account(p, ACCOUNTS[0], password)
page = browser.pages[0] if browser.pages else browser.new_page()
page.goto("https://example.com/account")
# ... the account's work; cookies and storage persist in profiles/store-us
browser.close()
将 XXXXX 后缀替换为你套餐页面上的用户名,该页面会列出每个用户名及其对应的城市和 ISP。配置文件目录(本例中为 profiles/store-us)是让账号在多次运行之间保持登录状态的关键:请备份它,且绝不要让两个账号指向同一个目录。
我们对映射检查、出口节点检查(针对已记录的响应)以及使用 Playwright Chromium 的浏览器选项都进行了测试。出口节点检查需要一个有效的 ISP 套餐才能针对真实代理运行。
让会话持续数小时
- 保持稳定、符合人类习惯的节奏。 一个人使用账号时会点击、阅读、等待。无论代理多好,每分钟上百次操作的爆发式行为看起来都像是自动化的。
- 复用已打开的浏览器。 在整个工作会话期间保持同一个浏览器打开,而不是为每个任务重新启动它。
- 长时间暂停后重新检查出口节点。 如果你在数小时后恢复工作,请再次运行出口节点检查。由于地址可能会刷新,网站偶尔可能会要求你确认登录;只要城市、服务商和配置文件保持一致,这就和真实用户在家中路由器重启后的体验一样。
- 不要为解决问题而将账号切换到另一个代理。 如果账号受到质疑,更换城市或服务商只会让情况更糟。应解决问题的根源,保持配对关系不变。
故障排查
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
407 Proxy Authentication Required | 用户名或密码错误 | 从套餐页面重新复制凭据;每个 IP 都有自己的用户名 |
| 出口节点检查报告城市不符 | 该用户名属于不同的分配 | 对照套餐页面上的列表检查用户名 |
| 每次运行后都被登出 | 每次都使用了新的浏览器配置文件 | 为每个账号使用持久的配置文件目录 |
| 反复出现验证提示 | 时区或语言与出口节点不匹配,或账号在不同代理间切换 | 根据出口节点的位置设置浏览器,并为每个账号保持一个代理 |
| 来自某个 IP 的请求缓慢或失败 | 该分配存在问题 | 运行文档中的连接检查,并带上用户名联系支持团队 |
关于负责任地使用账号的说明
这些技巧适用于你有权使用的账号:你自己的企业账号、经客户许可使用的客户账号,以及你自己服务上的测试账号。每个平台都有关于自动化以及一人或一家企业可运营多少账号的规定。一致的配置能避免合法工作被误判标记;它并非绕过平台规则的手段,也不应被当作这样的手段使用。
常见问题
我可以将轮换住宅代理用于长时间会话吗?
只能在一定程度上使用。Shifter 上的住宅粘性会话最长持续 30 分钟。对于持续时间更长的会话,或者每天都要返回的登录状态,应使用 ISP 代理,它会在整个计费周期内保持同一城市和 ISP。
ISP 代理的 IP 地址会变化吗?
在你的套餐处于活跃状态期间,城市和 ISP 保持固定不变。代理背后的 IP 地址可能会随时间刷新,就像家庭宽带连接一样。应围绕位置、服务商以及持久的浏览器配置文件来构建你的配置,这些要素保持不变。
一个 ISP 代理应该让多少个账号共享?
一个。为每个账号分配各自的代理,这样每个账号看起来都像是一个独立的家庭,且不会有两个账号看起来存在关联。
我需要持久的浏览器配置文件吗?
对于登录场景,是的。配置文件保存着让你保持登录状态的 Cookie 和存储数据。如果没有它,每次运行都会像一台全新设备,更容易触发验证要求。
结论
长时间会话和持久登录需要的是一致性,而非轮换。为每个账号分配一个位于该账号真实所在城市的专属 ISP 代理,为每个账号保留一个持久的浏览器配置文件,根据出口节点设置浏览器的时区和语言,并在每次开始前检查出口节点。做到这些后,账号每天看起来都一样,而这正是网站所要检验的全部内容。我们关于 ISP 代理与住宅代理的对比文章涵盖了两者之间更广泛的选择考量。