模拟登录与Cookie持久化:爬取中国汽车网用户专属榜单数据

一、技术核心:理解Session与Cookie

在HTTP这个无状态协议中,Cookie是服务端用来识别用户身份的关键技术。当您成功登录后,服务器会返回一个或多个Cookie(通常是Session ID),浏览器会在后续的请求中自动携带这些Cookie,从而向服务器证明"你是谁"。

在爬虫中模拟这一过程有两个关键步骤:

  1. 模拟登录(Simulated Login):通过向登录接口发送正确的凭证(如用户名、密码、验证码等),从服务器获取合法的Cookie。
  2. Cookie持久化(Cookie Persistence):将获取到的Cookie保存下来(如到文件或数据库),并在下次运行爬虫时重新加载。这样无需每次运行都重新登录,既避免了频繁登录可能触发的反爬机制,也大大提升了效率。

Python的**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Requests</font>**库中的**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Session</font>**对象完美地封装了这一切。它会自动管理和维护Cookies,在一次会话中保持所有请求的Cookie状态。

二、实战准备:分析目标网站登录流程

在编写代码之前,至关重要的第一步是使用浏览器的"开发者工具"(F12)手动分析登录过程。

  1. 打开登录页面:找到中国汽车网的登录入口。
  2. 捕捉登录请求
    • 在开发者工具中切换到 Network(网络) 选项卡。
    • 勾选 Preserve log(保留日志)
    • 输入错误的测试账号(如用户名:**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">test@example.com</font>**,密码:**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">test123</font>**)并点击登录。
    • 在网络请求列表中,找到一个**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">POST</font>**类型的请求,其名称往往是**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">login</font>**, **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">userLogin</font>**等,这就是我们要找的登录接口。
  3. 分析请求载荷(Request Payload)
    • 点击该登录请求,查看**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Headers</font>****<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Payload</font>**(或**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Form Data</font>**)。
    • 通常,我们会发现提交的数据不仅仅是用户名和密码,还可能包含隐藏的**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">token</font>****<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">csrfmiddlewaretoken</font>****<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">lt</font>**等用于防止CSRF攻击的验证参数。这些参数往往需要先从登录页面HTML中提取。

假设我们分析出的登录接口信息如下:

  • URL : **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">https://account.chinaautoweb.com/api/login</font>**
  • Method : **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">POST</font>**
  • Data :
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">username: your_username</font>**
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">password: your_password</font>**
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">token: (a dynamic value from the login page)</font>**

三、代码实现:四步走策略

我们将过程分解为四个步骤:获取登录令牌、执行登录、持久化Cookie、访问专属页面。

第1步:获取动态Token

许多网站的登录页面会嵌入一个动态的Token,我们需要先**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">GET</font>**登录页面,用解析库(如**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">lxml</font>**)将其提取出来。

plain 复制代码
import requests
from lxml import html

def get_login_token(session):
    login_page_url = 'https://account.chinaautoweb.com/login'
    
    # 使用session发起请求,保持cookie连贯性
    response = session.get(login_page_url)
    response.raise_for_status() # 检查请求是否成功
    
    # 解析HTML,寻找token
    tree = html.fromstring(response.text)
    # 使用XPath定位token元素。此XPath需根据实际网站结构修改!
    token = tree.xpath('//input[@name="token"]/@value')[0]
    return token
第2步:模拟登录并保存Session

获取Token后,将其与用户名、密码一起构造为表单数据,提交给登录接口。

plain 复制代码
def login(session, username, password):
    login_api_url = 'https://account.chinaautoweb.com/api/login'
    
    # 1. 首先获取token
    token = get_login_token(session)
    
    # 2. 构造登录数据
    login_data = {
        'username': username,
        'password': password,
        'token': token
    }
    
    # 3. 添加请求头,模拟浏览器行为
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Referer': 'https://account.chinaautoweb.com/login',
        'X-Requested-With': 'XMLHttpRequest' # 表明是Ajax请求
    }
    
    # 4. 发送POST请求
    response = session.post(login_api_url, data=login_data, headers=headers)
    response.raise_for_status()
    
    # 5. 检查登录是否成功(假设成功返回JSON中包含'code': 200)
    result = response.json()
    if result.get('code') == 200:
        print("登录成功!")
        return True
    else:
        print(f"登录失败: {result.get('message')}")
        return False
第3步:实现Cookie持久化

我们将Session中的Cookies转换为字典,然后用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">pickle</font>**模块序列化保存到本地文件。

plain 复制代码
import pickle
import os

def save_cookies(session, cookie_file):
    with open(cookie_file, 'wb') as f:
        # 将Session的cookies对象转为字典再保存
        pickle.dump(requests.utils.dict_from_cookiejar(session.cookies), f)

def load_cookies(session, cookie_file):
    if not os.path.exists(cookie_file):
        return False
    try:
        with open(cookie_file, 'rb') as f:
            cookies = pickle.load(f)
            # 将字典转换回cookiejar并更新到session中
            session.cookies = requests.utils.cookiejar_from_dict(cookies)
            print("Cookie加载成功!")
            return True
    except Exception as e:
        print(f"加载Cookie失败: {e}")
        return False
第4步:访问用户专属榜单并解析数据

现在,我们可以使用这个已经包含登录状态的Session去访问任何需要登录的页面了。

plain 复制代码
def fetch_user_ranking(session):
    ranking_url = 'https://www.chinaautoweb.com/user/favorite-ranking'
    
    response = session.get(ranking_url)
    response.raise_for_status()
    
    # 假设返回的是HTML页面
    tree = html.fromstring(response.text)
    
    # 使用XPath解析榜单数据(此处为示例,需根据实际页面调整)
    car_list = tree.xpath('//div[@class="rank-item"]')
    
    rankings = []
    for car in car_list:
        name = car.xpath('.//h3/text()')[0].strip()
        score = car.xpath('.//span[@class="score"]/text()')[0].strip()
        rankings.append({'车型': name, '热度得分': score})
    
    return rankings

四、主程序:串联整个流程

最后,我们编写一个主函数来串联所有步骤,实现智能登录:优先尝试加载旧Cookie,失败后再重新登录。

plain 复制代码
import requests
from lxml import html
import pickle
import os

# 代理配置信息
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"

# 构造代理格式
proxies = {
    'http': f'http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}',
    'https': f'http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}'
}

def get_login_token(session):
    login_page_url = 'https://account.chinaautoweb.com/login'
    response = session.get(login_page_url)
    response.raise_for_status()
    tree = html.fromstring(response.text)
    token = tree.xpath('//input[@name="token"]/@value')[0]
    return token

def login(session, username, password):
    login_api_url = 'https://account.chinaautoweb.com/api/login'
    token = get_login_token(session)
    
    login_data = {
        'username': username,
        'password': password,
        'token': token
    }
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Referer': 'https://account.chinaautoweb.com/login',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    response = session.post(login_api_url, data=login_data, headers=headers)
    response.raise_for_status()
    
    result = response.json()
    if result.get('code') == 200:
        print("登录成功!")
        return True
    else:
        print(f"登录失败: {result.get('message')}")
        return False

def save_cookies(session, cookie_file):
    with open(cookie_file, 'wb') as f:
        pickle.dump(requests.utils.dict_from_cookiejar(session.cookies), f)

def load_cookies(session, cookie_file):
    if not os.path.exists(cookie_file):
        return False
    try:
        with open(cookie_file, 'rb') as f:
            cookies = pickle.load(f)
            session.cookies = requests.utils.cookiejar_from_dict(cookies)
            print("Cookie加载成功!")
            return True
    except Exception as e:
        print(f"加载Cookie失败: {e}")
        return False

def fetch_user_ranking(session):
    ranking_url = 'https://www.chinaautoweb.com/user/favorite-ranking'
    response = session.get(ranking_url)
    response.raise_for_status()
    
    tree = html.fromstring(response.text)
    car_list = tree.xpath('//div[@class="rank-item"]')
    
    rankings = []
    for car in car_list:
        name = car.xpath('.//h3/text()')[0].strip()
        score = car.xpath('.//span[@class="score"]/text()')[0].strip()
        rankings.append({'车型': name, '热度得分': score})
    
    return rankings

def main():
    username = "your_actual_username"
    password = "your_actual_password"
    cookie_file = 'chinaautoweb_cookies.pkl'
    
    # 创建一个持久化的Session,并设置代理
    with requests.Session() as s:
        # 全局设置代理
        s.proxies = proxies
        
        # 尝试加载历史Cookie
        if load_cookies(s, cookie_file):
            print("检测到已保存的Session,尝试直接访问...")
            test_url = 'https://www.chinaautoweb.com/user/profile'
            test_resp = s.get(test_url)
            if test_resp.status_code == 200 and "我的资料" in test_resp.text:
                print("Session依然有效!")
            else:
                print("Session已失效,需要重新登录。")
                if not login(s, username, password):
                    return
                save_cookies(s, cookie_file)
        else:
            print("未找到保存的Session,开始登录...")
            if not login(s, username, password):
                return
            save_cookies(s, cookie_file)
        
        print("开始抓取用户专属榜单...")
        user_ranking_data = fetch_user_ranking(s)
        
        for idx, item in enumerate(user_ranking_data, 1):
            print(f"{idx}. {item['车型']} - 热度: {item['热度得分']}")

if __name__ == '__main__':
    main()

五、注意事项与进阶思考

  1. 动态变化:本文代码中的URL、XPath、表单字段名称均为示例,实际应用中必须根据目标网站的实际结构进行修改。
  2. 验证码(CAPTCHA):如果网站有复杂的验证码,需要引入图像识别(如TesseractOCR)或第三方打码平台服务。
  3. 加密参数 :愈来愈多的网站会对密码进行前端加密(如RSA, AES)或添加复杂的、由JavaScript生成的签名参数。这时仅靠**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Requests</font>**无法解决,需要使用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Selenium</font>****<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Playwright</font>**等自动化浏览器工具来模拟登录,或者直接逆向JavaScript代码。
  4. 伦理与法律 :务必遵守网站的**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">robots.txt</font>**协议,仅抓取被允许的公开或自有数据。避免对服务器造成过大压力,合理设置请求间隔。尊重用户隐私和数据版权。
相关推荐
usr_root2 小时前
【Qt中信号槽连接connect有接收者和无接收者的区别】
开发语言·c++·qt·命令模式
猫猫的小茶馆3 小时前
【C语言】汇编语言与C语言的混合编程
c语言·开发语言·stm32·单片机·嵌入式硬件·mcu·物联网
楼田莉子3 小时前
C++算法专题学习:模拟算法
开发语言·c++·学习·算法·leetcode
麦子邪3 小时前
C语言中奇技淫巧07-使用GCC栈保护选项检测程序栈溢出
linux·c语言·开发语言
我认不到你4 小时前
JVM分析(OOM、死锁、死循环)(JProfiler、arthas、jdk调优工具(命令行))
java·linux·开发语言·jvm·spring boot
zhong liu bin4 小时前
maven【maven】技术详解
java·ide·python·spring·maven·intellij-idea
扶尔魔ocy4 小时前
【QT特性技术讲解】QPrinter、QPdf
开发语言·qt
IAM四十二4 小时前
基于 Embedding 实现一个本地相册搜索功能
人工智能·python·llm
2401_858286115 小时前
CD75.【C++ Dev】异常
开发语言·c++·异常
魔力之心5 小时前
R notes[2]
开发语言·r语言