一、 为何JSP站点需要伪装?反爬虫机制探秘
在编写代码之前,理解我们的"对手"至关重要。JSP站点通常通过以下几种方式识别和拦截爬虫:
- User-Agent检测 :这是最基础的检测点。使用Python的
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库默认的User-Agent会直接暴露爬虫身份。 - 会话与Cookie管理 :JSP应用严重依赖
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">JSESSIONID</font>等Cookie来维持用户会话。不处理Cookie,就无法保持登录状态或通过某些验证流程。 - Referer验证 :某些图片或API接口会校验请求头中的
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Referer</font>字段,确保请求来源于站内页面,而非直接访问。 - 请求频率与行为模式:人类不会在秒级内发起大量请求。过高的访问频率是触发封禁的最快途径。
- JavaScript挑战 :部分JSP站点也会使用JavaScript进行简单的计算或跳转,虽然复杂度不及React/Vue应用,但足以拦截基础的
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库。 - IP地址封禁:当上述所有特征都指向爬虫时,服务器最终会记录并封禁您的IP地址。
二、 核心伪装策略:从"毛坯"到"精装"
我们的目标是将一个赤裸的HTTP请求,包装成一个由真实浏览器发出的、可信的请求。
策略一:完善HTTP请求头
这是伪装的第一步,也是最关键的一步。一个真实的浏览器请求头包含丰富的信息。
关键字段:
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">User-Agent</font>: 标识操作系统和浏览器类型。<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Referer</font>: 表明当前请求是从哪个页面链接过来的。<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Accept</font>: 声明客户端能接收的内容类型。<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Accept-Language</font>: 声明浏览器接受的语言。<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Connection</font>: 保持连接。
策略二:会话维持
使用<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests.Session()</font>对象。它会自动处理Cookie,在多次请求间保持会话状态,就像浏览器一样。
策略三:请求频率管理
在请求间引入随机延时,模拟人类阅读和点击的间隔。使用<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">time.sleep()</font>。
策略四:应对JavaScript(中级策略)
当简单的请求头伪装无效时,可能是遇到了JavaScript挑战。此时需要动用<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Selenium</font>或<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Playwright</font>等浏览器自动化工具,它们能驱动真实浏览器内核(如Chrome)执行页面上的所有JavaScript代码。
三、 代码实战:从基础到进阶
假设我们的目标是爬取一个名为 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">http://example-jsp-site.com/gallery.jsp</font> 的图片画廊。
1. 基础伪装:使用Requests + 请求头
plain
import requests
import time
import random
from bs4 import BeautifulSoup
# 定义一个常见的浏览器User-Agent列表,用于随机选择
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
]
# 创建一个会话对象
session = requests.Session()
# 为目标URL构造一个看起来真实的请求头
headers = {
'User-Agent': random.choice(USER_AGENTS),
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
}
session.headers.update(headers)
try:
# 首先访问主画廊页面,Referer可以是搜索引擎或站内首页
gallery_url = "http://example-jsp-site.com/gallery.jsp"
response = session.get(gallery_url, timeout=10)
response.raise_for_status() # 如果状态码不是200,则抛出异常
# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(response.text, 'html.parser')
# 假设图片链接在 <img> 标签的 `src` 属性中,且位于class为'gallery-img'的div下
image_elements = soup.select('div.gallery-img img')
for index, img in enumerate(image_elements):
# 构造完整的图片URL(处理相对路径)
img_src = img.get('src')
if img_src.startswith('/'):
img_url = "http://example-jsp-site.com" + img_src
else:
img_url = img_src
# 为图片请求设置Referer,表明是从gallery.jsp页面来的
img_headers = {'Referer': gallery_url}
# 请求图片内容
print(f"正在下载图片 {index+1}: {img_url}")
img_response = session.get(img_url, headers=img_headers)
img_response.raise_for_status()
# 将图片保存到本地
with open(f'image_{index+1}.jpg', 'wb') as f:
f.write(img_response.content)
# !!! 重要:在请求间添加随机延时,模拟人类行为 !!!
sleep_time = random.uniform(1, 3) # 随机等待1-3秒
time.sleep(sleep_time)
except requests.exceptions.RequestException as e:
print(f"网络请求出错: {e}")
2. 进阶伪装:使用Selenium应对复杂场景
如果目标站点必须执行JavaScript才能加载内容,<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>就无能为力了。这时需要<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Selenium</font>。
plain
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import os
# 代理配置信息
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"
# 配置Chrome选项,使其更接近真实用户
chrome_options = Options()
# 设置代理服务器
proxy_url = f"{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
chrome_options.add_argument(f'--proxy-server=http://{proxy_url}')
# 可选:无头模式(不显示浏览器界面)
# chrome_options.add_argument("--headless")
# 设置一个常见的用户代理
chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
# 禁用自动化测试标志,避免被检测为WebDriver
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
# 对于需要认证的代理,可以安装扩展来处理认证
from selenium.webdriver.chrome.options import Options as ChromeOptions
# 初始化WebDriver
driver = webdriver.Chrome(options=chrome_options) # 请确保chromedriver在PATH中
# 处理代理认证的替代方案(如果上面的方法不工作)
def enable_proxy_auth(proxy_host, proxy_port, proxy_username, proxy_password):
"""
通过执行JavaScript来设置代理认证
这是一个备选方案
"""
from selenium.webdriver.common.proxy import Proxy, ProxyType
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = f"{proxy_host}:{proxy_port}"
proxy.ssl_proxy = f"{proxy_host}:{proxy_port}"
# 创建代理认证扩展
auth_extension_path = create_proxy_auth_extension(proxy_host, proxy_port, proxy_username, proxy_password)
if auth_extension_path:
chrome_options.add_extension(auth_extension_path)
def create_proxy_auth_extension(proxy_host, proxy_port, proxy_username, proxy_password):
"""
创建代理认证扩展
"""
import zipfile
import os
import string
import random
# 生成扩展的manifest.json
manifest_json = """
{
"version": "1.0.0",
"manifest_version": 2,
"name": "Chrome Proxy",
"permissions": [
"proxy",
"tabs",
"unlimitedStorage",
"storage",
"<all_urls>",
"webRequest",
"webRequestBlocking"
],
"background": {
"scripts": ["background.js"]
},
"minimum_chrome_version":"22.0.0"
}
"""
# 生成背景脚本
background_js = """
var config = {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: "http",
host: "%s",
port: parseInt(%s)
},
bypassList: ["localhost"]
}
};
chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
function callbackFn(details) {
return {
authCredentials: {
username: "%s",
password: "%s"
}
};
}
chrome.webRequest.onAuthRequired.addListener(
callbackFn,
{urls: ["<all_urls>"]},
['blocking']
);
""" % (proxy_host, proxy_port, proxy_username, proxy_password)
# 创建临时扩展文件
extension_dir = 'proxy_auth_extension'
if not os.path.exists(extension_dir):
os.makedirs(extension_dir)
with open(os.path.join(extension_dir, "manifest.json"), "w") as f:
f.write(manifest_json)
with open(os.path.join(extension_dir, "background.js"), "w") as f:
f.write(background_js)
# 创建ZIP文件
extension_path = os.path.join(extension_dir, "extension.zip")
with zipfile.ZipFile(extension_path, 'w') as zp:
zp.write(os.path.join(extension_dir, "manifest.json"), "manifest.json")
zp.write(os.path.join(extension_dir, "background.js"), "background.js")
return extension_path
# 如果简单代理设置不工作,使用扩展方式
# enable_proxy_auth(proxyHost, proxyPort, proxyUser, proxyPass)
try:
# 访问目标页面
print("正在通过代理访问目标页面...")
driver.get("http://example-jsp-site.com/gallery.jsp")
# 使用显式等待,等待图片容器加载完成,而不是使用固定的time.sleep
wait = WebDriverWait(driver, 10)
# 假设图片加载在一个id为'imageContainer'的元素里
image_container = wait.until(EC.presence_of_element_located((By.ID, "imageContainer")))
# 在页面中执行JavaScript,模拟滚动以确保所有懒加载图片都被触发
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 等待滚动后加载
# 查找所有图片元素
img_elements = driver.find_elements(By.CSS_SELECTOR, "div.gallery-img img")
# 创建目录保存图片
if not os.path.exists('selenium_images'):
os.makedirs('selenium_images')
print(f"找到 {len(img_elements)} 张图片")
for index, img in enumerate(img_elements):
img_url = img.get_attribute('src')
print(f"通过Selenium获取到图片链接 {index+1}: {img_url}")
# 为了下载,我们可以使用requests会话,但需要传递Selenium获得的Cookie
# 或者,也可以直接通过Selenium截图,但这里演示用requests下载(更高效)
# 注意:通过Selenium获取的链接可能是动态加载的,直接用requests下载时可能需要保持相同的会话。
# 更稳妥的方法是继续使用Selenium来处理,或者将Cookie从Selenium传递给requests会话。
except Exception as e:
print(f"发生错误: {e}")
finally:
# 关闭浏览器
print("爬取完成,关闭浏览器...")
driver.quit()
# 清理临时扩展文件
import shutil
if os.path.exists('proxy_auth_extension'):
shutil.rmtree('proxy_auth_extension')
四、 策略总结与伦理规范
通过上述策略和代码,我们已经能够成功模拟一个正常浏览器对JSP站点的访问。我们来总结一下核心步骤:
- 伪装请求头 :使用真实、多样的
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">User-Agent</font>和其他头部信息。 - 维持会话 :使用
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Session</font>对象自动处理Cookies。 - 管理频率:在请求间引入随机延时,避免高频冲击。
- 处理动态内容 :当JS成为障碍时,升级使用
<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Selenium</font>或<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Playwright</font>。 - 遵守
**<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">robots.txt</font>**:在爬取前,检查目标网站的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">/robots.txt</font>文件,尊重网站管理员的意愿。
技术伦理提醒:爬虫技术是一把双刃剑。在实践过程中,请务必:
- 尊重数据版权和网站的服务条款。
- 控制访问压力,避免对目标网站的正常运营造成影响。
- 不爬取个人隐私和敏感数据。
- 将获取的数据用于合法、正当的目的。