Python爬虫实战:手把手教你爬取汽车之家二手车数据
·
爬虫程序设计思路
主要包含以下几个模块:
- 初始化模块:设置请求头、Cookie等信息
- 页面获取模块:向网页发起请求,获取网页内容
- 数据解析模块:解析HTML页面内容,提取二手车信息
- 数据清洗模块:清洗提取的数据,去除无用信息
- 数据存储模块:将清洗后的数据存储到CSV文件中
- 主程序模块:整合各个模块
完整代码实现
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
}
在初始化方法中,我们设置了请求头信息,包括accept、cookie和user-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
向目标网站发起请求,获取指定页码的网页内容:
- 构造URL:通过格式化字符串将页码插入到URL中
- 设置随机延迟:使用
time.sleep(random.uniform(1.5, 2.5))设置1.5到2.5秒的随机延迟,避免请求过于频繁被封IP - 更新User-Agent:每次请求前都更新User-Agent
- 发送请求:使用
requests.get()方法发送GET请求 - 状态码处理:
(1) 200:请求成功,返回网页内容
(2) 549:网络连接错误,最多进行5次重试 - 异常处理:捕获请求异常,打印错误信息并返回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页面,提取二手车信息:
- 解析HTML:使用
etree.HTML()方法将HTML字符串转换为可解析的对象 - 定位数据:使用XPath表达式定位二手车信息的容器
- 提取信息:
(1) 标题:使用normalize-space()函数去除多余的空格和换行符
(2) 行驶里程、年份:提取原始数据后调用clean_data()模块进行清洗
(3) 价格:直接提取价格信息 - 存储数据:将提取信息的字典添加到列表中,最后返回整个列表
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
清洗提取数据:
- 遍历数据:对传入的列表进行遍历
- 分割字符串:如果字符串中包含":",则分割字符串,取后面的部分
- 去除空格:使用
strip()方法去除字符串前后的空格 - 返回结果:将清洗后的数据添加到新列表中并返回
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文件中:
- 设置模式:第一次写入使用’w’模式,因为如果之后继续使用’w’,会将之前的数据覆盖,所以后续使用’a’模式(追加写入)
- 打开文件:使用
with语句打开文件,自动处理文件关闭 - 创建写入器:使用
csv.writer()创建CSV写入器 - 写入表头:如果是第一次写入,先写入表头
- 写入数据:遍历数据列表,将每条数据写入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到102页
- 获取页面:调用
get_html()方法获取网页内容 - 解析数据:如果获取到网页内容,调用
parse_html()方法解析数据 - 存储数据:调用
save_to_csv()方法存储数据,第一次时写入表头 - 完成提示:所有页面爬取完成后,打印提示信息
希望这篇文章对你学习Python爬虫有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论。
更多推荐
所有评论(0)