爬虫程序设计思路

主要包含以下几个模块:

  1. 初始化模块:设置请求头、Cookie等信息
  2. 页面获取模块:向网页发起请求,获取网页内容
  3. 数据解析模块:解析HTML页面内容,提取二手车信息
  4. 数据清洗模块:清洗提取的数据,去除无用信息
  5. 数据存储模块:将清洗后的数据存储到CSV文件中
  6. 主程序模块:整合各个模块

完整代码实现

import requests
import time
import random
import csv
from lxml import etree
from fake_useragent import UserAgent

class Spider:
    def __init__(self):
        # 设置伪装头
        self.headers = {
            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'cookie': 'fvlid=17353881118021AEEgoezg1; sessionid=C7C64EEC-1CB8-4CA3-AE30-2218F2C95181%7C%7C2024-12-28+20%3A15%3A11.792%7C%7Ccn.bing.com; autoid=db6c89a625941d84cbfe3eb7f532d52b; jrsfvi=1736853425490WcTNXDb627%7C0%7C6861993; historybbsName4=c-2197%7C%E5%A5%94%E9%A9%B0AMG; sessionuid=C7C64EEC-1CB8-4CA3-AE30-2218F2C95181%7C%7C2024-12-28+20%3A15%3A11.792%7C%7Ccn.bing.com; pcpopclub=77f54aaba8f44e349b71243cfb81935c11b0e7c3; clubUserShow=296806339|8200|2|%e5%b8%b8%e5%be%b7%e8%bd%a6%e5%8f%8b1121928|0|0|0|/g30/M0A/CC/43/120X120_0_q87_autohomecar__ChxknGT-zviALfCXAACbuINW9WI460.png|2025-09-01 20:25:23|0; sessionlogin=77f54aaba8f44e349b71243cfb81935c11b0e7c3; autouserid=296806339; __ah_uuid_ng=u_296806339; .thumbcache_16ba43f8aeefb62e85e0838b29118ea3=496BQcJ1+x5gH5alY+ff7f2swY/6PJq+36ybSoRdYWqHfq3UwhDaqZ/1ZyN7A0TLCvA062b1rJWPpauzoL8HWw%3D%3D; area=430799; sessionip=111.55.103.78; cityId=257; cookieCityId=430100; sessionvid=5963680A-EC2F-445F-9ADE-24332E5E660E; boypostion=300; ahpvno=33; v_no=4; visit_info_ad=C7C64EEC-1CB8-4CA3-AE30-2218F2C95181||5963680A-EC2F-445F-9ADE-24332E5E660E||-1||-1||4; ref=cn.bing.com%7C0%7C0%7Cchat.baidu.com%7C2025-11-04+09%3A56%3A09.684%7C2025-09-07+08%3A46%3A38.979',
            'user-agent': UserAgent().random
        }

    def get_html(self, page_num):
        """
        :param page_num: 页数
        :return: 网页内容
        """
        url = 'https://car.autohome.com.cn/2sc/changsha/a0_0msdgscncgpi1ltocsp{}ex/'.format(page_num)
        try:
            time.sleep(random.uniform(1.5, 2.5))
            # 每次请求前,都更换一次ua,防止被反爬
            self.headers['user-agent'] = UserAgent().random
            response = requests.get(url, headers=self.headers, timeout=10)
            # 如果状态码为200,说明请求成功,返回网页内容
            if response.status_code == 200:
                return response.text
            elif response.status_code == 549:
                print('状态码为{}, 网络连接错误, 正在重试'.format(response.status_code))
                for i in range(1, 6):
                    time.sleep(random.uniform(5, 10))
                    try:
                        response = requests.get(url, headers=self.headers)
                        return response.text
                    except Exception as e:
                        print('第{}次重试失败: {}'.format(i, e))
        except requests.exceptions.RequestException as req_err:
            print('请求错误:{}'.format(req_err))
            time.sleep(random.uniform(3, 5))
            return None

    def clean_data(self, info):
        """
        清洗数据
        :param info: 需要进行清洗的数据列表
        :return: 清洗后的数据列表
        """
        info_list = []
        for i in info:
            # 判断字符串中是否有':'
            if ':' in i:
                # 分割之后, 去掉空格
                i = i.split(":")[1].strip()
            else:
                i = i.strip()
            info_list.append(i)
        return info_list

    def parse_html(self, data, page):
        """
        :param data: 网页的内容
        :return: 解析后的二手车信息列表
        """
        # 将HTML转为XML
        tree = etree.HTML(data)
        content_all = tree.xpath('//li[@order="0"]')
        # 存储二手车信息
        SecondCar_list = []
        for content in content_all:
            # 标题
            title = content.xpath('.//div[@class="title"]//text()[normalize-space()]')
            title = title[0].strip() if title else ""
            # 原始的行驶里程数据 - 需要进行清洗
            ori_travel_distance = content.xpath('.//div[@class="detail-l"]/p[1]/text()')
            # 清洗后的行驶里程数据
            travel_distance = self.clean_data(ori_travel_distance)
            # 原始的年份数据 - 需要进行清洗
            ori_year = content.xpath('.//div[@class="detail-l"]/p[2]/text()')
            # 清洗后的年份数据
            year = self.clean_data(ori_year)
            # 价格
            price = content.xpath('.//span[@class="colf8"]/text()')
            price = price[0].strip() if price else ""
            SecondCar_info = {
                'title': title,
                # 处理结果为空的数据
                'travel_distance': travel_distance[0] if travel_distance else "",
                # 处理结果为空的数据
                'year': year[0] if year else "",
                'price': price
            }
            # 将每次获取的二手车信息追加到列表中
            SecondCar_list.append(SecondCar_info)
        print('---------------------第{}页的数据获取完成---------------------'.format(page))
        return SecondCar_list

    def save_to_csv(self, data_list, first_write=False):
        """
        存储到csv文件中
        :param data_list: 二手车信息列表
        :param first_write: 是否是第一次写入文件
        :return:
        """
        # 这里设置mode的原因是,第一次写入文件的时候用w(写入),因为如果一直使用写入,会覆盖上一次的数据,之后用a(追加)
        mode = 'w' if first_write else 'a'
        with open('汽车之家-长沙二手车信息.csv', mode, newline='', encoding='utf-8-sig') as f:
            write = csv.writer(f)
            if first_write:
                # 写入表头
                write.writerow(['标题', '行驶里程', '年份', '价格(单位: 万)'])
            for data in data_list:
                # 分批次写入
                write.writerow([data['title'], data['travel_distance'], data['year'], data['price']])

    def run(self):
        """
        主程序:用来整合程序的功能模块
        """
        for i in range(1, 103):
            html = self.get_html(i)
            # 判断网页内容是否为空
            if html:
                information = self.parse_html(html, i)
                # 存储到csv文件中
                self.save_to_csv(information, i == 1)
        print("所有页面数据爬取完成!")

if __name__ == '__main__':
    # 实例化类
    SecondCar = Spider()

    # 主程序
    SecondCar.run()

代码解析

1. 初始化模块

def __init__(self):
    # 设置伪装头
    self.headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        'cookie': 'your_cookie_here',
        'user-agent': UserAgent().random
    }

在初始化方法中,我们设置了请求头信息,包括acceptcookieuser-agent。其中:

(1) accept:告诉服务器我们可以接受的内容类型
(2) cookie:用于模拟登录状态,绕过一些反爬机制
(3) user-agent:使用fake-useragent库生成随机的User-Agent,避免被服务器识别为爬虫

2. 页面获取模块

def get_html(self, page_num):
    url = 'https://car.autohome.com.cn/2sc/changsha/a0_0msdgscncgpi1ltocsp{}ex/'.format(page_num)
    try:
        time.sleep(random.uniform(1.5, 2.5))
        self.headers['user-agent'] = UserAgent().random
        response = requests.get(url, headers=self.headers, timeout=10)
        if response.status_code == 200:
            return response.text
        elif response.status_code == 549:
            print('状态码为{}, 网络连接错误, 正在重试'.format(response.status_code))
            for i in range(1, 6):
                time.sleep(random.uniform(5, 10))
                try:
                    response = requests.get(url, headers=self.headers)
                    return response.text
                except Exception as e:
                    print('第{}次重试失败: {}'.format(i, e))
    except requests.exceptions.RequestException as req_err:
        print('请求错误:{}'.format(req_err))
        time.sleep(random.uniform(3, 5))
        return None

向目标网站发起请求,获取指定页码的网页内容:

  1. 构造URL:通过格式化字符串将页码插入到URL中
  2. 设置随机延迟:使用time.sleep(random.uniform(1.5, 2.5))设置1.5到2.5秒的随机延迟,避免请求过于频繁被封IP
  3. 更新User-Agent:每次请求前都更新User-Agent
  4. 发送请求:使用requests.get()方法发送GET请求
  5. 状态码处理:
    (1) 200:请求成功,返回网页内容
    (2) 549:网络连接错误,最多进行5次重试
  6. 异常处理:捕获请求异常,打印错误信息并返回None

3. 数据解析模块

def parse_html(self, data, page):
    tree = etree.HTML(data)
    content_all = tree.xpath('//li[@order="0"]')
    SecondCar_list = []
    for content in content_all:
        # 标题
        title = content.xpath('.//div[@class="title"]//text()[normalize-space()]')
        title = title[0].strip() if title else ""
        # 原始的行驶里程数据
        ori_travel_distance = content.xpath('.//div[@class="detail-l"]/p[1]/text()')
        travel_distance = self.clean_data(ori_travel_distance)
        # 原始的年份数据
        ori_year = content.xpath('.//div[@class="detail-l"]/p[2]/text()')
        year = self.clean_data(ori_year)
        # 价格
        price = content.xpath('.//span[@class="colf8"]/text()')
        price = price[0].strip() if price else ""
        SecondCar_info = {
            'title': title,
            'travel_distance': travel_distance[0] if travel_distance else "",
            'year': year[0] if year else "",
            'price': price
        }
        SecondCar_list.append(SecondCar_info)
    print('---------------------第{}页的数据获取完成---------------------'.format(page))
    return SecondCar_list

使用lxml库解析HTML页面,提取二手车信息:

  1. 解析HTML:使用etree.HTML()方法将HTML字符串转换为可解析的对象
  2. 定位数据:使用XPath表达式定位二手车信息的容器
  3. 提取信息:
    (1) 标题:使用normalize-space()函数去除多余的空格和换行符
    (2) 行驶里程、年份:提取原始数据后调用clean_data()模块进行清洗
    (3) 价格:直接提取价格信息
  4. 存储数据:将提取信息的字典添加到列表中,最后返回整个列表

4. 数据清洗模块

def clean_data(self, info):
    info_list = []
    for i in info:
        if ':' in i:
            i = i.split(":")[1].strip()
        else:
            i = i.strip()
        info_list.append(i)
    return info_list

清洗提取数据:

  1. 遍历数据:对传入的列表进行遍历
  2. 分割字符串:如果字符串中包含":",则分割字符串,取后面的部分
  3. 去除空格:使用strip()方法去除字符串前后的空格
  4. 返回结果:将清洗后的数据添加到新列表中并返回

5. 数据存储模块

def save_to_csv(self, data_list, first_write=False):
    mode = 'w' if first_write else 'a'
    with open('汽车之家-长沙二手车信息.csv', mode, newline='', encoding='utf-8-sig') as f:
        write = csv.writer(f)
        if first_write:
            write.writerow(['标题', '行驶里程', '年份', '价格(单位: 万)'])
        for data in data_list:
            write.writerow([data['title'], data['travel_distance'], data['year'], data['price']])

在这里插入图片描述

将清洗后的数据存储到CSV文件中:

  1. 设置模式:第一次写入使用’w’模式,因为如果之后继续使用’w’,会将之前的数据覆盖,所以后续使用’a’模式(追加写入)
  2. 打开文件:使用with语句打开文件,自动处理文件关闭
  3. 创建写入器:使用csv.writer()创建CSV写入器
  4. 写入表头:如果是第一次写入,先写入表头
  5. 写入数据:遍历数据列表,将每条数据写入CSV文件

6. 主程序模块

def run(self):
    for i in range(1, 103):
        html = self.get_html(i)
        if html:
            information = self.parse_html(html, i)
            self.save_to_csv(information, i == 1)
    print("所有页面数据爬取完成!")

主程序模块负责整个爬虫的流程:

  1. 遍历页码:从1到102页
  2. 获取页面:调用get_html()方法获取网页内容
  3. 解析数据:如果获取到网页内容,调用parse_html()方法解析数据
  4. 存储数据:调用save_to_csv()方法存储数据,第一次时写入表头
  5. 完成提示:所有页面爬取完成后,打印提示信息

希望这篇文章对你学习Python爬虫有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐