作为一名长期从事自动化开发的工程师,我最近接到一个社交媒体运营自动化的需求。客户需要在Medium平台上维持多个账号的活跃度,但手动操作效率低下且难以规模化。经过技术调研,我决定采用Python+Playwright的技术方案来实现这一目标。
为什么选择Playwright而不是传统的Selenium?主要基于三点考虑:
重要提示:任何自动化工具的使用都必须遵守平台的服务条款。Medium允许合理的自动化操作,但禁止spam和滥用行为。
我推荐使用Python 3.10+版本,这个版本在性能和稳定性之间取得了很好的平衡。以下是详细的安装步骤:
bash复制# 对于Linux/macOS用户
brew install python@3.11 # 使用Homebrew安装
# 或者
pyenv install 3.11.6 # 使用pyenv安装特定版本
# 对于Windows用户
# 从Python官网下载安装包时,务必勾选"Add Python to PATH"
验证安装是否成功:
bash复制python --version
# 应该显示类似 Python 3.11.6 的输出
传统pip在大型项目中会遇到依赖解析慢的问题。UV是Astral公司(也是Ruff linting工具的开发者)推出的新一代Python包管理器,用Rust编写,速度极快。
安装命令:
bash复制pip install uv
使用UV创建虚拟环境:
bash复制uv venv .venv # 创建虚拟环境
source .venv/bin/activate # 激活(Linux/macOS)
# 或者 .venv\Scripts\activate (Windows)
Playwright的一大优势是它会自动下载所需的浏览器二进制文件,无需手动安装浏览器。
完整安装步骤:
bash复制uv add playwright # 使用uv安装
playwright install # 安装所有支持的浏览器
playwright install chromium # 仅安装Chromium
如果要指定Chrome 140版本(项目需求),需要额外配置:
bash复制PLAYWRIGHT_CHROMIUM_VERSION=140 playwright install chromium
合理的项目结构能大大提高后期维护效率。我推荐如下结构:
code复制medium_automation/
├── .env # 环境变量
├── config/
│ ├── accounts.yaml # 账号配置
│ └── settings.py # 全局设置
├── modules/
│ ├── browser.py # 浏览器控制
│ ├── actions.py # 操作模块
│ └── utils.py # 工具函数
├── logs/ # 日志目录
└── main.py # 主入口
使用python-dotenv管理敏感信息,避免硬编码:
python复制# .env文件示例
MEDIUM_USERNAME=your_username
MEDIUM_PASSWORD=your_password
PROXY_URL=http://proxy.example.com:8080
加载环境变量的代码:
python复制from dotenv import load_dotenv
import os
load_dotenv()
username = os.getenv('MEDIUM_USERNAME')
password = os.getenv('MEDIUM_PASSWORD')
Playwright提供了同步和异步两种API。对于大多数自动化场景,同步API更简单直接:
python复制from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https://medium.com')
# 后续操作...
browser.close()
为了更好模拟人类行为,需要配置一些启动参数:
python复制browser = p.chromium.launch(
headless=False,
args=[
'--disable-blink-features=AutomationControlled',
'--start-maximized'
],
channel="chrome",
executable_path="/path/to/chrome140" # 如果使用特定版本
)
使用BrowserContext可以隔离不同的用户会话:
python复制context = browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...',
locale='en-US',
timezone_id='America/New_York'
)
page = context.new_page()
现代网站会收集大量浏览器指纹信息。我们可以通过以下方式降低被检测风险:
python复制context = browser.new_context(
viewport={'width': 1366, 'height': 768},
device_scale_factor=1,
is_mobile=False,
has_touch=False,
java_script_enabled=True,
# 更多参数...
)
人类操作具有随机性和不完美性,我们需要在代码中模拟这些特点:
python复制import random
import time
def human_like_delay(min=1, max=3):
time.sleep(random.uniform(min, max))
def human_like_scroll(page):
scroll_height = random.randint(300, 800)
page.mouse.wheel(0, scroll_height)
human_like_delay(0.5, 1.5)
设置合理的操作间隔和随机停顿:
python复制def random_wait(base=2, variation=1.5):
"""基础等待时间 + 随机变化"""
wait_time = base + random.random() * variation
time.sleep(wait_time)
# 使用示例
page.click('button')
random_wait(base=3, variation=2) # 等待3-5秒
自动化脚本需要处理各种可能的异常情况:
python复制from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
try:
page.click('button:has-text("Follow")', timeout=5000)
except PlaywrightTimeoutError:
print("Follow button not found within 5 seconds")
# 可以尝试其他选择器或记录错误
except Exception as e:
print(f"Unexpected error: {str(e)}")
# 其他错误处理逻辑
使用Python标准库logging记录详细执行日志:
python复制import logging
from datetime import datetime
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/execution.log'),
logging.StreamHandler()
]
)
def log_action(action, account=None, status="success", details=""):
logging.info(f"[{account or 'SYSTEM'}] {action} - {status} | {details}")
python复制def medium_login(page, username, password):
page.goto('https://medium.com')
human_like_delay(2, 4)
# 点击登录按钮
page.click('a[href*="signin"]')
human_like_delay(1, 2)
# 选择邮箱登录方式
page.click('button:has-text("Continue with email")')
human_like_delay(1, 2)
# 输入凭据
page.fill('input[type="email"]', username)
human_like_delay(0.5, 1.5)
page.click('button:has-text("Continue")')
human_like_delay(1, 2)
page.fill('input[type="password"]', password)
human_like_delay(0.5, 1.5)
page.click('button:has-text("Sign in")')
human_like_delay(3, 5) # 等待登录完成
# 验证登录是否成功
if page.is_visible('a[href*="me"]'):
log_action("Login", username, "success")
return True
else:
log_action("Login", username, "failed", "Cannot find profile link")
return False
python复制def browse_and_interact(page, max_articles=5):
page.goto('https://medium.com/topic/popular')
human_like_delay(3, 5)
articles = page.locator('article').all()
random.shuffle(articles) # 随机顺序浏览
for i, article in enumerate(articles[:max_articles]):
# 随机滚动浏览
human_like_scroll(page)
# 随机决定是否打开文章
if random.random() > 0.3:
article.click()
human_like_delay(5, 10) # 阅读时间
# 随机互动
if random.random() > 0.7:
page.click('button[aria-label="Clap"]')
human_like_delay(1, 2)
if random.random() > 0.8:
comments = page.locator('button:has-text("Respond")')
if comments.count() > 0:
comments.first.click()
human_like_delay(1, 2)
page.fill('textarea', generate_random_comment())
human_like_delay(2, 3)
if random.random() > 0.5: # 50%概率实际提交
page.click('button:has-text("Respond")')
human_like_delay(2, 3)
page.go_back()
human_like_delay(3, 5)
python复制context = browser.new_context(
proxy={
"server": "http://myproxy.com:8080",
"username": "user",
"password": "pass"
},
geolocation={
"latitude": 40.7128,
"longitude": -74.0060,
"accuracy": 90
},
permissions=["geolocation"]
)
python复制# 示例:拦截图片加载提升性能
def route_handler(route):
if route.request.resource_type == "image":
route.abort()
else:
route.continue_()
page.route("**/*", route_handler)
建议设置以下监控指标:
可以使用Prometheus + Grafana搭建监控看板,或者简单的发送邮件/短信告警。
建议实施以下安全措施:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 元素找不到 | 页面未完全加载/选择器错误 | 增加等待时间/使用更稳定的选择器 |
| 操作被阻止 | 反爬检测触发 | 调整行为模式/更换IP/增加延迟 |
| 登录失败 | 验证码出现/账号异常 | 手动处理验证码/检查账号状态 |
| 性能下降 | 资源占用过高 | 优化代码/减少并发数/升级硬件 |
bash复制playwright codegen https://medium.com
python复制context.tracing.start(screenshots=True, snapshots=True)
# ...执行操作...
context.tracing.stop(path="trace.zip")
python复制browser = p.chromium.launch(headless=False, slow_mo=100) # 100ms延迟
python复制page.on("console", lambda msg: print(f"CONSOLE: {msg.text}"))
在实际项目中,我发现最有效的调试方式是组合使用这些方法。例如先通过录制功能获取基础代码,然后添加详细的日志记录,遇到复杂问题时再启用追踪功能。