Selenium 与爬虫的关联
在 Selenium 普及之前,基于 Webkit 的无界面浏览器 PhantomJS 曾是自动化测试的主流工具。但 Selenium 直接运行在真实浏览器中,模拟用户操作更加自然,数据提取能力更强,目前已基本取代了 PhantomJS。
Selenium 的核心作用
- 捕获任意形式的数据:
page_source属性能返回页面加载完成后的所有源码,无论是静态 HTML 还是 Ajax 动态加载的内容,真正实现了"可见即可爬取"。虽然效率略低于纯 HTTP 请求,但准确性高。 - 模拟登录:对于反爬机制复杂的大型网站,Selenium 能模拟真实的浏览器行为,绕过部分验证逻辑。
Selenium 捕获动态加载数据
环境准备
首先需要下载 chromedriver,这是驱动 Chrome 浏览器的核心组件。务必确保驱动版本与你本地安装的 Chrome 浏览器版本一致。在地址栏输入 chrome://version/ 可查看当前版本号。
安装依赖:
pip install selenium
案例:药监总局企业数据爬取
该网站数据通过 Ajax 动态加载,URL 固定,点击页码会触发后台请求。利用 Selenium,我们无需关心 Ajax 细节,直接解析渲染后的 DOM 即可。
先尝试爬取第一页数据:
from selenium import webdriver
from lxml import etree
import time
# 指定 chromedriver 路径,建议配置环境变量
bro = webdriver.Chrome(executable_path='../chromedriver_win32/chromedriver.exe')
url = 'http://125.35.6.84:81/xk/'
bro.get(url)
# page_source 获取渲染后的完整 HTML
page_text = bro.page_source
# 解析企业名称
tree = etree.HTML(page_text)
li_list = tree.xpath('//*[@id="gzlist"]/li')
for li in li_list:
name = li.xpath('./dl/@title')[0]
print(name)
bro.quit()
如何爬取多页数据?
由于 URL 不变,我们需要模拟点击"下一页"按钮。注意添加延时等待数据加载。
from selenium import webdriver
from lxml import etree
import time
bro = webdriver.Chrome(executable_path='../chromedriver_win32/chromedriver.exe')
url = 'http://125.35.6.84:81/xk/'
bro.get(url)
all_page_text = []
# 控制翻页次数
for i in range(3):
# 定位下一页按钮并点击
next_page_tag = bro.find_element_by_xpath('//*[@id="pageIto_next"]')
next_page_tag.click()
time.sleep(2) # 等待 Ajax 请求完成
all_page_text.append(bro.page_source)
# 批量解析
for page_text in all_page_text:
tree = etree.HTML(page_text)
li_list = tree.xpath('//*[@id="gzlist"]/li')
for li in li_list:
name = li.xpath('./dl/@title')[0]
print(name)
bro.quit()
Selenium 的优势在于它就是一个可控制的真实浏览器,无需处理复杂的反爬策略,只需掌握元素定位(XPath、ID、Class)和 page_source 的使用即可。
Selenium 模拟登录:以 12306 为例
简单的古诗文登录可能不需要截图,但像 12306 这种带有图形验证码的网站,不能单独请求验证码图片,因为 Selenium 打开页面时已经触发了一次请求,再次请求会导致验证码不一致。
解决方案:截屏后裁剪验证码区域,再送入打码平台。
前置准备:
pip install Pillow
代码实现:
from PIL import Image
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
import time
bro = webdriver.Chrome(executable_path='../chromedriver_win32/chromedriver.exe')
url = 'https://kyfw.12306.cn/otn/login/init'
bro.get(url)
time.sleep(2) # 等待验证码加载
# 输入账号密码
username_tag = bro.find_element(By.ID, 'username')
username_tag.send_keys('你的用户名')
password_tag = bro.find_element(By.ID, 'password')
password_tag.send_keys('你的密码')
# 截屏并裁剪验证码
bro.save_screenshot('./main.png')
code_img_tag = bro.find_element(By.XPATH, '//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
location = code_img_tag.location
size = code_img_tag.size
# 计算裁剪坐标
rect = (int(location['x']), int(location['y']),
int(location['x'] + size['width']), int(location['y'] + size['height']))
i = Image.open('./main.png')
frame = i.crop(rect)
frame.save('./code.png')
# TODO: 此处调用超级鹰等打码接口
# result = transform_code_img('./code.png', 9004)
# 假设已获取坐标结果,格式为 x1,y1|x2,y2...
result = "100,100|200,200"
# 解析坐标
all_list = []
if '|' in result:
list1 = result.split('|')
for item in list1:
xy_list = [int(item.split(',')[0]), int(item.split(',')[1])]
all_list.append(xy_list)
else:
all_list.append([int(result.split(',')[0]), int(result.split(',')[1])])
# 使用动作链点击验证码
for position in all_list:
x, y = position
ActionChains(bro).move_to_element_with_offset(code_img_tag, x, y).click().perform()
time.sleep(1)
# 点击登录
bro.find_element(By.ID, 'loginSub').click()
time.sleep(3)
# 验证登录成功
page_str = bro.page_source
print("登录状态检查...")
bro.quit()
注意事项:
- 电脑屏幕缩放比例必须设置为 100%,否则坐标计算会偏移。
- 打码服务需要充值积分,请替换为自己的 API Key。
JS 加密与混淆机制分析
案例:空气质量查询网
该网站使用了较为复杂的 JS 加密和混淆技术。响应数据是密文,前端通过回调函数解密显示。
逆向思路:
- 抓包:找到 Ajax 请求,观察参数
d(动态加密)和 Response(加密数据)。 - 定位源码:搜索绑定搜索按钮的事件函数,追踪到发起 Ajax 的
getServerData函数。 - 反混淆:JS 混淆旨在隐藏逻辑,可使用在线工具或手动还原。
- JS 逆向执行:在 Python 中调用 JS 函数生成参数和解密数据。
工具准备:
- 安装 Node.js 环境(PyExecJS 依赖)。
- 安装 PyExecJS:
pip install PyExecJS
步骤一:生成加密参数 d
将反混淆后的 JS 代码保存为 test.js,并在末尾添加封装函数:
function getPostParamCode(method, city, type, startTime, endTime){
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
return getParam(method, param);
}
Python 端调用:
import execjs
node = execjs.get()
file = 'test.js'
ctx = node.compile(open(file, encoding='utf-8').read())
method = 'GETDETAIL'
city = '北京'
type_ = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(
method, city, type_, start_time, end_time)
params = ctx.eval(js)
print(params)
步骤二:解密 Response
拿到加密的 Response 后,同样通过 JS 函数解密:
response_text = requests.post(url, data={'d': params}).content.decode()
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)
说明:
部分网站服务器可能不稳定,导致无法获取有效数据,但逆向流程本身是通用的。核心在于找到前端负责加密和解密的 JS 源码,并通过 PyExecJS 桥接至 Python 环境执行。
总结
JS 逆向的核心在于理解前端逻辑。手动改写为 Python 代码可行但繁琐,推荐使用 PyExecJS 配合 Node.js 环境。遇到混淆代码时,善用反混淆工具,结合浏览器调试器逐步追踪函数调用链。


