原理
第一步发送请求
与浏览器请求访问服务器地址一样,python程序向服务器发送访问请求,服务器返回数据。
在python中我们可以使用
第二步解析网页内容
浏览器在接收到服务器返回的数据后,会自行解析内容最后呈现出我们所看到的界面。但是在程序中我们收到的内容是一大串数据,我们需要分析这些数据去拿到我们想要的内容。
第三步储存和分析数据
详细过程
我们以汽车之家二手车内容为例(我随便找的网站)
发送请求
可以使用python中的requests库,来构建get请求
import requests
# 定义headers参数,修改 User-Agent来模拟浏览器的请求访问
# 如果不写user-agent,服务器会知道这个get请求是来自于程序发出的而不是浏览器,对于一些不希望被爬取的网站可能会拒绝访问
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0"}
# 传入headers参数,修改指定信息
response = requests.get('https://www.che168.com/china/20_30/a0_0msdgscncgpi1ltocsp1exx0/?pvareaid=102179#currengpostion', headers=headers)
if response.ok:
# 使用response.text查看请求结果的内容
print("请求成功")
# print(response.text)
else:
print("响应失败")
输出结果如下:
可以看到,我们请求响应得到的结果是一个htm文件格式的内容,这是对应网页的源码,就与我们在该网页查看详细源码的结果一样
user-agent的查询方法:进入该网站的详细页面,点击网络
解析内容
根据个人需求,我们拟定的获取目标如下:即一辆车的售价、行使公里数、上牌日期、出售地、车名
可以使用python中的BeautifulSoup库,来解析html文件,解析后的解构为树状结构,方便程序查询想要的内容,如下图:
通过在网页源码端口的详细查询,我们得知了汽车名字的信息如下:h4标签,class类别是'card-name'。
所以我们可以使用BeautifulSoup来解析和查找对应信息,代码如下:
import requests
from bs4 import BeautifulSoup
# 定义headers参数,修改 User-Agent来模拟浏览器的请求访问
# 如果不写user-agent,服务器会知道这个get请求是来自于程序发出的而不是浏览器,对于一些不希望被爬取的网站可能会拒绝访问
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0"}
# 传入headers参数,修改指定信息
response = requests.get('https://www.che168.com/china/20_30/a0_0msdgscncgpi1ltocsp1exx0/?pvareaid=102179#currengpostion', headers=headers)
if response.ok:
print("请求成功")
# 将得到的HTML文本转换成BeautifulSoup对象
# 因为BeautifulSoup不光可以解析HTML文件,使用需要指定解析器为HTML解析器
soup = BeautifulSoup(response.text, 'html.parser')
# 查找标签为h4,class_='card-name'的元素,返回一个列表
car_name = soup.findAll('h4', class_='card-name')
for i in car_name:
# 因为返回的列表是标签类型,需要取出文本所以加上.string
print(i.string)
else:
print("响应失败")
运行结果:
辆车的售价、行使公里数、上牌日期、出售地、车名的整个解析:
由于一辆车的所有信息都放在一个<li>标签下,为了保证信息一一对应,我们放弃之前查询全部<h4>标签的做法,转而先查询所有<li>标签,然后再查询该<li>标签下的汽车名字、价格、信息等,这样就可以做到信息的对应。
对应代码如下:
# 汽车之家二手车信息爬取
import requests
from bs4 import BeautifulSoup
# 定义headers参数,修改 User-Agent来模拟浏览器的请求访问
# 如果不写user-agent,服务器会知道这个get请求是来自于程序发出的而不是浏览器,对于一些不希望被爬取的网站可能会拒绝访问
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0"}
# 传入headers参数,修改指定信息
response = requests.get('https://www.che168.com/china/20_30/a0_0msdgscncgpi1ltocsp1exx0/?pvareaid=102179#currengpostion', headers=headers)
if response.ok:
print("请求成功")
# 创建列表储存数据
all_data = []
# 将得到的HTML文本转换成BeautifulSoup对象
# 因为BeautifulSoup不光可以解析HTML文件,使用需要指定解析器为HTML解析器
soup = BeautifulSoup(response.text, 'html.parser')
# 找到所有li标签
all_tag_li = soup.find_all('li', class_='cards-li list-photo-li')
# 在每个li标签中找到想要的信息
for tag_li in all_tag_li:
try:
# 价格
price = tag_li.find('span', class_='pirce').find('em').text
# 名字
name = tag_li.find('h4', class_='card-name').text
# 信息
info = tag_li.find('p', class_='cards-unit').text
# 加入到列表中
all_data.append([price, name, info])
except AttributeError:
# 对于个别li标签不完整的,忽略并继续下一个
continue
for data in all_data:
print(data)
else:
print("响应失败")
运行结果:
结果储存
根据需求选择储存方式,这里我选项保存到本地csv文件中。代码如下:
# 汽车之家二手车信息爬取
import requests
from bs4 import BeautifulSoup
import csv
# 定义headers参数,修改 User-Agent来模拟浏览器的请求访问
# 如果不写user-agent,服务器会知道这个get请求是来自于程序发出的而不是浏览器,对于一些不希望被爬取的网站可能会拒绝访问
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0"}
# 传入headers参数,修改指定信息
response = requests.get('https://www.che168.com/china/20_30/a0_0msdgscncgpi1ltocsp1exx0/?pvareaid=102179#currengpostion', headers=headers)
if response.ok:
print("请求成功")
# 创建列表储存数据
all_data = []
# 将得到的HTML文本转换成BeautifulSoup对象
# 因为BeautifulSoup不光可以解析HTML文件,使用需要指定解析器为HTML解析器
soup = BeautifulSoup(response.text, 'html.parser')
# 找到所有li标签
all_tag_li = soup.find_all('li', class_='cards-li list-photo-li')
# 在每个li标签中找到想要的信息
for tag_li in all_tag_li:
try:
# 价格
price = tag_li.find('span', class_='pirce').find('em').text
# 名字
name = tag_li.find('h4', class_='card-name').text
# 信息
info = tag_li.find('p', class_='cards-unit').text
# 加入到列表中
all_data.append([price, name, info])
except AttributeError:
# 对于个别li标签不完整的,忽略并继续下一个
continue
# 定义CSV文件的路径为当前python文件所在路径目录下,名称为car_data.csv
csv_file_path = './car_data.csv'
with open(csv_file_path, 'w', newline='', encoding='utf-8') as csv_file:
# 创建CSV写入器
csv_writer = csv.writer(csv_file)
# 写入CSV文件的表头
csv_writer.writerow(['价格', '名称', '信息'])
# 写入CSV文件的每一行数据
csv_writer.writerows(all_data)
print(f"数据已保存到 {csv_file_path}")
else:
print("响应失败")
运行结果:
优化程序
1.上述代码只能爬取一页的内容,我们需要把整个100页内的所有汽车信息爬取下来。
2.由于<li>标签内的汽车信息是分布在一个<p>标签内的,导致爬取到的汽车公里数、上牌时间、出售地点没有得到分开,我们需要分别存储这些信息。
最终代码如下:
# 汽车之家二手车信息爬取
import os
import requests
from bs4 import BeautifulSoup
import csv
def download(page):
# 定义headers参数,修改 User-Agent来模拟浏览器的请求访问
# 如果不写user-agent,服务器会知道这个get请求是来自于程序发出的而不是浏览器,对于一些不希望被爬取的网站可能会拒绝访问
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0"}
# 传入headers参数,修改指定信息
response = requests.get(f'https://www.che168.com/china/20_30/a0_0msdgscncgpi1ltocsp{page}exx0/?pvareaid=102179#currengpostion',headers=headers)
if response.ok:
# 创建列表储存数据
all_data = []
# 将得到的HTML文本转换成BeautifulSoup对象
# 因为BeautifulSoup不光可以解析HTML文件,使用需要指定解析器为HTML解析器
soup = BeautifulSoup(response.text, 'html.parser')
# 找到所有li标签
all_tag_li = soup.find_all('li', class_='cards-li list-photo-li')
# 在每个li标签中找到想要的信息
for tag_li in all_tag_li:
try:
# 价格
price = tag_li.find('span', class_='pirce').find('em').text
# 名字
name = tag_li.find('h4', class_='card-name').text
# 信息
info = tag_li.find('p', class_='cards-unit').text
# 切割信息
info_list = info.split('/')
# 里程
mileage = info_list[0]
# 上牌日期
date = info_list[1]
# 出售地点
location = info_list[2]
# 加入到列表中
all_data.append([price, name, mileage, date, location])
except AttributeError:
# 对于个别li标签不完整的,忽略并继续下一个
continue
# 定义CSV文件的路径为当前python文件所在路径目录下,名称为car_data.csv
csv_file_path = './car_data.csv'
# 检查CSV文件是否存在
if os.path.exists(csv_file_path):
# 如果CSV文件存在,则追加写入
with open(csv_file_path, 'a', newline='', encoding='utf-8') as csv_file:
# 创建CSV写入器
csv_writer = csv.writer(csv_file)
# 写入CSV文件的每一行数据
csv_writer.writerows(all_data)
else:
# 如果CSV文件不存在,则创建并写入
with open(csv_file_path, 'w', newline='', encoding='utf-8') as csv_file:
# 创建CSV写入器
csv_writer = csv.writer(csv_file)
# 写入CSV文件的表头
csv_writer.writerow(['价格', '名称', '里程', '上牌日期', '出售地点'])
# 写入CSV文件的每一行数据
csv_writer.writerows(all_data)
print(f"网页{page}爬取成功")
else:
print("响应失败")
if __name__ == "__main__":
# 取100页数据
for i in range(1, 101):
download(i)
print("所有网页取完成")
运行结果:
csv文件展示:
存在的问题
最后爬取到的数据只有217条,显然是远远少于实际数据的。问题出在了内容解析的时候,因为每个<li>标签不能保证都是完整的按照我们的格式来的,例如有的<li>标签缺少'span', class_='pirce'、有的<li>标签缺少'h4', class_='card-name',所以在爬取不完整标签的时候会报错AttributeError,这里我使用了异常捕获跳过了报错,同时也跳过了爬取这条不完整的数据。
后续解决了我会更新代码的。
更新:
因为在<li>标签中的class类,有的是cards-li list-photo-li cxc-card,有的是cards-li list-photo-li,所以在爬取的时候我们的代码只爬取了类别为cards-li list-photo-li的数据。
解决办法很简单,我们直接搜索指定目录下的所有<li>标签即可。
由于之前的代码headers只有user-agent,连续爬取几次后就会被网站拒绝访问,所以在headers中多添加了几个参数,让我们的爬虫程序看起来更像浏览器的访问操作。同时加入系统沉睡,限制程序每一秒只访问一个页面,减少被拒绝访问的风险。
参数的获取方法与user-agent相同
最终源码
# 汽车之家二手车信息爬取
import os
import time
import requests
from bs4 import BeautifulSoup
import csv
def download(page):
# 定义headers参数,修改 User-Agent来模拟浏览器的请求访问
# 如果不写user-agent,服务器会知道这个get请求是来自于程序发出的而不是浏览器,对于一些不希望被爬取的网站可能会拒绝访问
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',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cache-Control': 'max-age=0',
'priority': 'u=0, i',
'upgrade-insecure-requests': '1',
'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0",
'Cookie': 'fvlid=1722413416137LVe9FhE7rzhg; sessionid=f4912b41-3403-445e-b9d4-4fc459234c54; sessionip=10.132.4.114; area=0; Hm_lvt_d381ec2f88158113b9b76f14c497ed48=1722413417; HMACCOUNT=055AE983BAABD8CA; che_sessionid=41B341E6-588E-4350-BC8B-0271539DE377%7C%7C2024-07-31+16%3A10%3A17.436%7C%7Cwww.autohome.com.cn; UsedCarBrowseHistory=0%3A51459576; carDownPrice=1; userarea=0; listuserarea=0; sessionvisit=c00aac7b-cd18-4f3d-9495-566e1ada5783; sessionvisitInfo=f4912b41-3403-445e-b9d4-4fc459234c54||0; che_sessionvid=5B005A7D-BFFD-46DD-8FF7-52B4D66F5ED6; ahpvno=34; Hm_lpvt_d381ec2f88158113b9b76f14c497ed48=1722431981; showNum=34; ahuuid=EEBCCA8E-4EA4-4FF4-BD14-05E154C3AFEF; v_no=34; visit_info_ad=41B341E6-588E-4350-BC8B-0271539DE377||5B005A7D-BFFD-46DD-8FF7-52B4D66F5ED6||-1||-1||34; che_ref=www.autohome.com.cn%7C0%7C100444%7C0%7C2024-07-31+21%3A19%3A42.067%7C2024-07-31+16%3A10%3A17.436; sessionuid=f4912b41-3403-445e-b9d4-4fc459234c54'
}
# 传入headers参数,修改指定信息
response = requests.get(f'https://www.che168.com/china/20_30/a0_0msdgscncgpi1ltocsp{page}exx0/?pvareaid=102179#currengpostion',headers=headers)
if response.ok:
# 创建列表储存数据
all_data = []
# 将得到的HTML文本转换成BeautifulSoup对象
# 因为BeautifulSoup不光可以解析HTML文件,使用需要指定解析器为HTML解析器
soup = BeautifulSoup(response.text, 'html.parser')
# 找到所有li标签
all_tag_li = soup.find('div', class_='content fn-clear card-wrap') \
.find('div', class_='tp-cards-tofu fn-clear') \
.find('ul', class_='viewlist_ul').find_all('li')
# 在每个li标签中找到想要的信息
for tag_li in all_tag_li:
try:
# 价格
price = tag_li.find('span', class_='pirce').find('em').text
# 名字
name = tag_li.find('h4', class_='card-name').text
# 信息
info = tag_li.find('p', class_='cards-unit').text
# 切割信息
info_list = info.split('/')
# 里程
mileage = info_list[0]
# 上牌日期
date = info_list[1]
# 出售地点
location = info_list[2]
# 加入到列表中
all_data.append([price, name, mileage, date, location])
except AttributeError:
# 对于个别li标签不完整的,忽略并继续下一个
continue
# 定义CSV文件的路径为当前python文件所在路径目录下,名称为car_data.csv
csv_file_path = './car_data.csv'
# 检查CSV文件是否存在
if os.path.exists(csv_file_path):
# 如果CSV文件存在,则追加写入
with open(csv_file_path, 'a', newline='', encoding='utf-8') as csv_file:
# 创建CSV写入器
csv_writer = csv.writer(csv_file)
# 写入CSV文件的每一行数据
csv_writer.writerows(all_data)
else:
# 如果CSV文件不存在,则创建并写入
with open(csv_file_path, 'w', newline='', encoding='utf-8') as csv_file:
# 创建CSV写入器
csv_writer = csv.writer(csv_file)
# 写入CSV文件的表头
csv_writer.writerow(['价格', '名称', '里程', '上牌日期', '出售地点'])
# 写入CSV文件的每一行数据
csv_writer.writerows(all_data)
print(f"网页{page}爬取成功,找到{len(all_data)}条数据")
else:
print("响应失败")
if __name__ == "__main__":
# 取100页数据
for i in range(1, 101):
try:
download(i)
except AttributeError:
print(f"网页{i}取失败")
time.sleep(1) # 沉睡1秒,避免被网站拒绝
print("所有网页取完成")
结果如下:
CSV结果如下:
最后爬取到了八千多条数据