前阵子帮同学爬某乎的行业问答数据,刚爬5页就栽了——登录页弹出滑块验证码,手滑输错3次直接锁账号;换个小号手动登录,没爬2小时Cookie就过期,又得重新输密码;最头疼的是,哪怕登录成功,连续爬十几页就会提示“访问异常”,IP没封但就是看不了内容。
折腾一周后才摸透:某乎的反爬对“非人类操作”几乎零容忍,直接用requests爬会被秒识别,手动维护Cookie又太费时间。最后靠Selenium模拟登录+Cookie池的组合,终于实现“一次搭建,长期免登录爬取”,稳定跑了半个月没翻车。这篇就把实战细节拆解开,从环境准备到代码落地,连滑块验证码的轨迹都给你调好,新手跟着做也能成。
一、先搞懂:某乎反爬的3个“拦路虎”
在动手前,得先明白某乎是怎么识别爬虫的,不然再厉害的工具也白搭:
登录验证门槛高:普通账号登录要滑块验证码,新账号甚至要短信验证,直接用账号密码POST请求根本过不了;Cookie时效性短:手动登录获取的Cookie,最多活24-48小时,过期后必须重新登录,频繁登录还会触发账号风控;Cookie与IP绑定:某乎会检测Cookie对应的IP,若用AIP登录获取Cookie,再用BIP爬数据,会被判定为“异常登录”,直接失效。
所以单纯用Selenium登录不够,还得搭个Cookie池——自动存储多个有效Cookie,定时验证有效性,失效了自动补新的,这样既不用每次手动登录,也能避免单一Cookie失效导致爬取中断。
二、实战1:Selenium模拟登录某乎,搞定滑块验证码
Selenium的核心作用是“模拟真人操作浏览器登录”,能绕过某乎的登录反爬(比如滑块验证)。这里重点讲怎么定位登录元素、处理滑块验证码,以及避免被某乎识别为“机器人浏览器”。
1. 环境准备:3分钟装好依赖
需要2个核心工具:Selenium(控制浏览器)和webdriver-manager(自动管理浏览器驱动,不用手动下载):
pip install selenium webdriver-manager
webdriver-manager会自动匹配你的Chrome版本下载驱动,避免出现“驱动版本不兼容”的坑——之前手动下载驱动,Chrome一更新就报错,用这个工具后再也没踩过这坑。
2. 核心代码:Selenium模拟登录(含滑块验证码处理)
某乎登录页的元素会偶尔微调,这里用XPATH定位(比ID定位更稳定),滑块验证码用“先加速后减速+轻微抖动”的轨迹模拟人类操作:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import random
def simulate_login_zhihu(username, password):
"""
Selenium模拟登录某乎
:param username: 账号(手机号/邮箱)
:param password: 密码
:return: 登录成功后的Cookie列表
"""
# 1. 配置Chrome选项,避免被某乎识别为爬虫
chrome_options = webdriver.ChromeOptions()
# 无头模式(后台运行,可选,调试时可注释)
# chrome_options.add_argument("--headless=new")
# 禁用GPU、沙盒模式,减少识别特征
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
# 禁用自动化扩展提示
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option("useAutomationExtension", False)
# 设置User-Agent,模拟真实浏览器
chrome_options.add_argument(
"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
)
# 2. 启动Chrome浏览器
driver = webdriver.Chrome(
service=ChromeService(ChromeDriverManager().install()),
options=chrome_options
)
# 隐藏webdriver特征(关键!某乎会检测这个)
driver.execute_cdp_cmd("page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
try:
# 3. 打开某乎登录页(用账号密码登录入口,不是验证码登录)
driver.get("https://www.zhihu.com/signin?next=/")
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//button[text()='密码登录']"))
).click()
# 4. 输入账号密码
# 定位账号输入框(XPATH://input[@name='username'])
username_input = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//input[@name='username']"))
)
username_input.send_keys(username)
# 定位密码输入框(XPATH://input[@name='password'])
password_input = driver.find_element(By.XPATH, "//input[@name='password']")
password_input.send_keys(password)
# 点击登录按钮
driver.find_element(By.XPATH, "//button[@type='submit']").click()
time.sleep(2) # 等待可能出现的滑块验证码
# 5. 处理滑块验证码(某乎登录常见,没出现则跳过)
try:
# 定位滑块(XPATH://div[@class='Slider-thumb'])
slider = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.XPATH, "//div[@class='Slider-thumb']"))
)
# 定位滑道,计算滑动距离(滑道宽度 - 滑块宽度)
slider_track = driver.find_element(By.XPATH, "//div[@class='Slider-track']")
track_width = slider_track.size["width"]
thumb_width = slider.size["width"]
slide_distance = track_width - thumb_width
# 生成人类滑动轨迹(先加速后减速,带抖动)
def generate_slide_track(distance):
track = []
current = 0
# 加速阶段(前60%距离)
while current < distance * 0.6:
speed = random.randint(5, 10)
track.append(speed)
current += speed
# 减速阶段(后40%距离)
while current < distance:
speed = random.randint(1, 3)
track.append(speed)
current += speed
# 加一点抖动,更像人
track[-5:] = [2, 1, 1, 0, 1]
return track
track = generate_slide_track(slide_distance)
# 模拟滑动:按住滑块→按轨迹滑动→松开
action = ActionChains(driver)
action.click_and_hold(slider).perform() # 按住
for step in track:
action.move_by_offset(step, random.randint(-1, 1)).perform() # 横向滑动,纵向轻微抖动
time.sleep(random.uniform(0.01, 0.03)) # 每步延迟,避免太快
time.sleep(random.uniform(0.1, 0.2)) # 滑到终点停顿
action.release().perform() # 松开
print("滑块验证码处理完成")
except Exception as e:
print("未出现滑块验证码或处理失败:", str(e))
# 6. 验证登录成功(访问个人中心,看是否有用户名)
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.XPATH, "//span[@class='UserLink-name']"))
)
print("登录成功!")
# 7. 提取Cookie(转为字典格式,方便后续使用)
cookies = driver.get_cookies()
cookie_dict = {cookie["name"]: cookie["value"] for cookie in cookies}
return cookie_dict
except Exception as e:
print("登录失败:", str(e))
return None
finally:
driver.quit() # 关闭浏览器
# 测试:替换成你的账号密码
if __name__ == "__main__":
zhihu_cookie = simulate_login_zhihu("你的手机号/邮箱", "你的密码")
if zhihu_cookie:
print("获取到Cookie:", zhihu_cookie)
关键避坑点(亲测踩过的坑):
Selenium被某乎识别:一定要加的CDP命令,不然某乎会检测到
hide webdriver,直接判定为爬虫,登录按钮点了没反应;滑块验证码总失败:轨迹不能匀速!必须先快后慢,加轻微纵向抖动(比如
navigator.webdriver=True),之前用匀速滑动,连续失败5次,改轨迹后成功率90%+;元素定位超时:用
random.randint(-1,1)显式等待(等元素加载出来再操作),别用
WebDriverWait,某乎加载速度不稳定,有时快有时慢。
time.sleep(10)
三、实战2:搭建Cookie池,告别频繁登录
Cookie池的核心是“存储多个有效Cookie,定时更新,随用随取”。这里用Redis存储Cookie(支持过期时间、快速查询),搭配定时任务验证Cookie有效性,失效了自动用Selenium补新Cookie。
1. 环境准备:安装Redis和依赖库
# 安装Redis(Windows用Redis Desktop Manager,Linux直接apt install redis-server)
# 安装Python操作Redis的库
pip install redis apscheduler # apscheduler用于定时任务
2. Cookie池核心功能:4步实现自动化
Cookie池要做4件事:获取Cookie→存储Cookie→验证有效性→定时更新,代码分模块实现:
(1)Redis连接配置
import redis
import json
import requests
# 连接Redis(本地Redis默认配置,若远程需改host和password)
redis_client = redis.Redis(
host="localhost",
port=6379,
db=0,
decode_responses=True # 自动解码为字符串,避免b''格式
)
# Cookie池的Redis键前缀(区分其他数据)
COOKIE_KEY_PREFIX = "zhihu_cookie_"
(2)获取Cookie(调用之前的Selenium登录函数)
def get_new_cookie(username, password):
"""获取新Cookie(调用Selenium登录)"""
cookie_dict = simulate_login_zhihu(username, password)
if cookie_dict:
# 用账号作为Redis键,存储Cookie(JSON格式)
redis_key = f"{COOKIE_KEY_PREFIX}{username}"
redis_client.set(redis_key, json.dumps(cookie_dict), ex=86400) # 过期时间24小时
print(f"新Cookie已存储:{redis_key}")
return cookie_dict
return None
(3)验证Cookie有效性
判断Cookie是否有效的方法:用Cookie访问某乎个人中心,看是否返回200且包含“个人设置”等登录后才有的内容:
def is_cookie_valid(cookie_dict):
"""验证Cookie是否有效"""
if not cookie_dict:
return False
# 转换Cookie为请求头格式
cookies = {k: v for k, v in cookie_dict.items()}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
}
try:
# 访问个人中心,验证是否登录
response = requests.get(
"https://www.zhihu.com/settings/profile",
headers=headers,
cookies=cookies,
timeout=10
)
# 若状态码200且包含“个人资料设置”,说明Cookie有效
if response.status_code == 200 and "个人资料设置" in response.text:
return True
else:
print(f"Cookie无效,状态码:{response.status_code}")
return False
except Exception as e:
print(f"Cookie验证失败:{str(e)}")
return False
(4)定时更新Cookie池
用APScheduler每2小时验证一次所有Cookie,失效的自动重新获取(需要准备多个某乎账号,避免单一账号风控):
from apscheduler.schedulers.blocking import BlockingScheduler
# 准备多个某乎账号(避免单一账号频繁登录被风控)
ZHIHU_ACCOUNTS = [
("账号1手机号/邮箱", "账号1密码"),
("账号2手机号/邮箱", "账号2密码"),
# 可新增更多账号
]
def update_cookie_pool():
"""更新Cookie池:验证所有Cookie,失效则重新获取"""
print("开始更新Cookie池...")
for username, password in ZHIHU_ACCOUNTS:
redis_key = f"{COOKIE_KEY_PREFIX}{username}"
# 从Redis获取Cookie
cookie_json = redis_client.get(redis_key)
if cookie_json:
cookie_dict = json.loads(cookie_json)
# 验证Cookie有效性
if is_cookie_valid(cookie_dict):
print(f"Cookie有效:{username}")
continue
else:
print(f"Cookie失效,重新获取:{username}")
# 重新获取Cookie并存储
get_new_cookie(username, password)
print("Cookie池更新完成!")
# 启动定时任务:每2小时更新一次
if __name__ == "__main__":
scheduler = BlockingScheduler()
# 立即执行一次,然后每2小时执行一次
scheduler.add_job(update_cookie_pool, "interval", hours=2, id="zhihu_cookie_update")
print("Cookie池定时更新任务已启动,每2小时更新一次...")
scheduler.start()
四、综合实战:用Cookie池免登录爬某乎问答
有了Cookie池,爬数据时不用每次登录,直接从池里取一个有效Cookie,就能免登录访问某乎内容:
def crawl_zhihu_answers(topic_url, count=10):
"""
用Cookie池爬某乎话题下的问答
:param topic_url: 话题URL(如“https://www.zhihu.com/topic/19552836/questions”)
:param count: 要爬的问答数量
:return: 问答列表
"""
# 1. 从Cookie池取一个有效Cookie
valid_cookie = None
# 遍历所有账号的Cookie,找一个有效的
for username, _ in ZHIHU_ACCOUNTS:
redis_key = f"{COOKIE_KEY_PREFIX}{username}"
cookie_json = redis_client.get(redis_key)
if cookie_json:
cookie_dict = json.loads(cookie_json)
if is_cookie_valid(cookie_dict):
valid_cookie = cookie_dict
print(f"使用有效Cookie:{username}")
break
if not valid_cookie:
print("Cookie池无有效Cookie,爬取失败!")
return []
# 2. 爬取话题下的问答
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
"Referer": "https://www.zhihu.com/"
}
answers = []
page = 1
while len(answers) < count:
# 构造分页URL(某乎话题问答分页参数为“page”)
page_url = f"{topic_url}?page={page}"
try:
response = requests.get(
page_url,
headers=headers,
cookies=valid_cookie,
timeout=10
)
response.encoding = "utf-8"
if response.status_code != 200:
print(f"爬取第{page}页失败,状态码:{response.status_code}")
# 切换Cookie重试(可能当前Cookie突然失效)
valid_cookie = None
for username, _ in ZHIHU_ACCOUNTS:
redis_key = f"{COOKIE_KEY_PREFIX}{username}"
cookie_dict = json.loads(redis_client.get(redis_key))
if is_cookie_valid(cookie_dict):
valid_cookie = cookie_dict
print(f"切换Cookie:{username}")
break
if not valid_cookie:
break
continue
# 3. 解析问答内容(用XPATH提取标题和链接,根据实际页面调整)
from lxml import etree
html = etree.HTML(response.text)
question_items = html.xpath("//div[@class='List-item']")
for item in question_items:
if len(answers) >= count:
break
# 提取问题标题
title = item.xpath(".//h2[@class='QuestionHeader-title']/text()")
title = title[0].strip() if title else "无标题"
# 提取问题链接
link = item.xpath(".//a[@class='QuestionLink']/@href")
link = "https://www.zhihu.com" + link[0] if link else ""
answers.append({"title": title, "link": link})
print(f"已爬取第{page}页,共{len(answers)}个问答")
page += 1
time.sleep(random.uniform(1, 3)) # 加随机延迟,避免反爬
except Exception as e:
print(f"爬取第{page}页出错:{str(e)}")
time.sleep(5)
page += 1
return answers
# 测试:爬“Python”话题下的20个问答
if __name__ == "__main__":
topic_url = "https://www.zhihu.com/topic/19552836/questions"
answers = crawl_zhihu_answers(topic_url, count=20)
print("爬取完成,问答列表:")
for idx, ans in enumerate(answers, 1):
print(f"{idx}. {ans['title']} → {ans['link']}")
五、Cookie池+Selenium避坑指南(90%新手会踩)
账号风控问题:不要用一个账号频繁登录!至少准备3个以上小号,轮流获取Cookie,不然某乎会提示“账号存在安全风险”,临时冻结登录;Redis存储失效:Cookie的过期时间设为24小时,定时任务每2小时更新一次,避免Cookie过期后没及时补,导致爬取中断;Selenium滑块总失败:如果某乎弹出“请拖动滑块完成安全验证”但滑块不显示,是因为IP被标记了——此时换个代理IP再试(Cookie池可搭配代理池使用,效果更好);页面结构变化:某乎会偶尔调整页面CSS类名(比如改成
List-item),导致XPATH定位不到元素——遇到这种情况,按F12重新检查元素的XPATH,更新代码即可。
List-item--withAction
最后:这个方案的真正价值
用Selenium+Cookie池爬某乎,不只是“突破反爬”,更重要的是“解放双手”——之前手动登录、换Cookie,每天要花半小时处理登录问题,现在Cookie池自动维护,爬数据时只需取Cookie就够了,效率翻了3倍。
如果你的爬取量不大(每天几千条数据),这个方案完全够用;如果爬取量很大,建议再搭配代理池(比如阿布云隧道代理),避免单一IP被封。
最后说句实话:某乎的反爬规则一直在变,比如最近新增了“登录后需验证手机号”的情况,这时可以在Selenium里加个短信验证码接收的API(比如阿里云短信服务),不过新手先把基础的Cookie池跑通再说。
如果爬的时候遇到奇奇怪怪的问题,比如Cookie突然全部失效、Selenium被检测,评论区留言,咱们一起拆解解决方案~





















暂无评论内容