Python爬虫伪装策略:如何模拟浏览器正常访问JSP站点

一、 为何JSP站点需要伪装?反爬虫机制探秘

在编写代码之前,理解我们的"对手"至关重要。JSP站点通常通过以下几种方式识别和拦截爬虫:

  1. User-Agent检测 :这是最基础的检测点。使用Python的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库默认的User-Agent会直接暴露爬虫身份。
  2. 会话与Cookie管理 :JSP应用严重依赖<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">JSESSIONID</font>等Cookie来维持用户会话。不处理Cookie,就无法保持登录状态或通过某些验证流程。
  3. Referer验证 :某些图片或API接口会校验请求头中的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Referer</font>字段,确保请求来源于站内页面,而非直接访问。
  4. 请求频率与行为模式:人类不会在秒级内发起大量请求。过高的访问频率是触发封禁的最快途径。
  5. JavaScript挑战 :部分JSP站点也会使用JavaScript进行简单的计算或跳转,虽然复杂度不及React/Vue应用,但足以拦截基础的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>库。
  6. 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站点的访问。我们来总结一下核心步骤:

  1. 伪装请求头 :使用真实、多样的<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">User-Agent</font>和其他头部信息。
  2. 维持会话 :使用<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Session</font>对象自动处理Cookies。
  3. 管理频率:在请求间引入随机延时,避免高频冲击。
  4. 处理动态内容 :当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>
  5. 遵守 **<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>文件,尊重网站管理员的意愿。

技术伦理提醒:爬虫技术是一把双刃剑。在实践过程中,请务必:

  • 尊重数据版权和网站的服务条款。
  • 控制访问压力,避免对目标网站的正常运营造成影响。
  • 不爬取个人隐私和敏感数据
  • 将获取的数据用于合法、正当的目的
相关推荐
SEO_juper1 小时前
别再纠结LLMs.txt了!它背后的真相与最佳使用场景,一文讲透。
开发语言·ai·php·数字营销
程序员西西2 小时前
SpringBoot接口安全:APIKey保护指南
java·spring boot·计算机·程序员·编程·编程开发
g***B7382 小时前
JavaScript在Node.js中的模块系统
开发语言·javascript·node.js
summer_west_fish2 小时前
单体VS微服务:架构选择实战指南
java·微服务·架构
v***8572 小时前
Ubuntu介绍、与centos的区别、基于VMware安装Ubuntu Server 22.04、配置远程连接、安装jdk+Tomcat
java·ubuntu·centos
烤麻辣烫2 小时前
黑马程序员大事件后端概览(表现效果升级版)
java·开发语言·学习·spring·intellij-idea
q***96582 小时前
Spring总结(上)
java·spring·rpc
思密吗喽2 小时前
宠物商城系统
java·开发语言·vue·毕业设计·springboot·课程设计·宠物
csbysj20202 小时前
Lua 函数
开发语言