DrissionPage实战案例:小红书旅游数据爬取
目标
爬取小红书旅游相关内容数据,包括:
-
笔记标题
-
图片数量
-
点赞数
-
收藏数
-
评论数
-
详情页URL
-
下载笔记中的所有图片
技术栈
-
DrissionPage:用于自动化浏览器操作和数据包监听
-
Requests:用于发送HTTP请求
-
正则表达式:用于解析HTML内容
-
CSV模块:用于数据存储
代码
# 导入模块
import csv
from DrissionPage import ChromiumPage
import requests
import re
import time
import random
import os
# 创建xhs_data来存储数据
base_dir = 'xhs_data'
if not os.path.exists(base_dir):
os.mkdir(base_dir)
# 创建页面
page = ChromiumPage()
# 获取标签页
tab = page.latest_tab
# 开始监听数据包
tab.listen.start('api/sns/web/v1/search/notes')
print('准备打开页面,等待页面加载完成')
# 打开页面
tab.get('https://www.xiaohongshu.com/search_result?keyword=%25E6%2597%2585%25E6%25B8%25B8&source=web_explore_feed')
# 等待页面加载完成
tab.wait.load_start()
"""获取数据"""
# 获取第一个数据包
res = tab.listen.wait()
# 获取响应的数据
dict_data = res.response.body
items_list = []
data_count = 1
try:
while True:
# 退出死循环条件
if dict_data['data']['has_more'] == False:
print('总共加载数据条数为', len(items_list))
break
else:
items = dict_data['data']['items']
items_list.append(items)
print(f'第{data_count}个数据包获取完成')
time.sleep(random.uniform(1, 3))
data_count += 1
# 第1次滚动
tab.scroll.to_bottom()
# 获取下一个数据包,并且等待时间为5秒
res = tab.listen.wait(timeout=5)
# 如果第一次滚动之后没有数据包加载,则继续进行滚动
if not res:
tab.run_js('window.scrollBy(0,2500)')
# 获取数据包,并且时间设置为5秒
res = tab.listen.wait(timeout=5)
# 如果还没有数据包,那么就表示已经全部加载完毕了
if not res:
print('数据可能到达底部')
# 获取响应的数据
dict_data = res.response.body
except Exception as e:
print('获取数据包失败',e)
# 构造请求头
headers = {
'user-angent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
'cookie':'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
}
try:
with open(os.path.join(base_dir,'xhs.csv'),'w',newline='',encoding='utf-8') as f:
# 定义表头,并且写入
fieldnames = ['标题', '图片数量', '点赞数', '评论数', '收藏数', '详情页url']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
"""处理数据"""
for items in items_list:
try:
for i,item in enumerate(items):
# 定义字典,用于打印结果,并且写入csv文件
my_data_dict = {
'标题': '',
'图片数量': '',
'点赞数': '',
'评论数': '',
'收藏数': '',
'详情页url': ''
}
# 获取发布笔记的id
note_id = item['id']
# 获取发布笔记的xsec_token
xsec_token = item['xsec_token']
# 分析详情页url地址
# https://www.xiaohongshu.com/explore/679e13ca000000002902c13b?xsec_token=AB2xkQBCe8dz6uVelB_qg4jrO1UjgHPHU64XBQ8Qd236w=&xsec_source=pc_search&source=web_explore_feed
# 发现679e13ca000000002902c13b是获取的id,AB2xkQBCe8dz6uVelB_qg4jrO1UjgHPHU64XBQ8Qd236w=&xsec_source=为获取的xsec_token
# 构造url
url = f'https://www.xiaohongshu.com/explore/{note_id}?xsec_token={xsec_token}&xsec_source=pc_search&source=web_explore_feed'
my_data_dict['详情页url'] = url
try:
# 随机1~3秒,发送请求
time.sleep(random.uniform(1, 3))
# 发送请求,获取响应
response = requests.get(url, headers=headers)
# 如果响应码为200
if response.status_code == 200:
html = response.text
# print(html)
# 提取title数据
title_match = re.search(r'<title>(.*?) - 小红书</title>',html,re.S)
if title_match:
old_title = title_match.group(1)
# 进行安全名的处理
title = re.sub(r'[\\/:*?"<>|\t\r\n]', '', old_title)[:50]
# 创建目录,用来存放图片
note_file = os.path.join(base_dir,title)
my_data_dict['标题'] = title
if not os.path.exists(note_file):
os.makedirs(note_file)
else:
my_data_dict['标题'] = '无法获取'
print(f'第{i+1}篇笔记无法获取标题')
# 从HTML中提取所有的图片链接
image_urls = re.findall(r'<meta name="og:image" content="(.*?)">', html,re.S)
if image_urls and title:
print(f'获取到{len(image_urls)}张图片~')
my_data_dict['图片数量'] = len(image_urls)
# 再次发送请求,请求下载图片
for j,image_url in enumerate(image_urls):
response_img_download = requests.get(image_url,headers=headers,timeout=20)
# 如果状态码等于200,则执行以下代码
if response_img_download.status_code == 200:
if os.path.exists(note_file):
# 如果文件夹存在,则写入
# 写入文件
try:
with open(os.path.join(note_file, f'image_{j}.jpg'), 'wb') as f:
f.write(response_img_download.content)
print(f'已保存{j + 1}张图片')
except Exception as e:
print(f'保存第{j + 1}图片失败')
# 非则先创建文件夹,再写入
else:
os.makedirs(note_file)
try:
with open(os.path.join(note_file, f'image_{j}.jpg'), 'wb') as f:
f.write(response_img_download.content)
print(f'已保存{j + 1}张图片')
except Exception as e:
print(f'保存第{j + 1}图片失败')
else:
print(f'请求第{j}张图片失败,无法获取响应')
else:
print(f'第{i+1}篇笔记无法获取详情页图片url,或者因为没有获取到标题,无法创建文件')
my_data_dict['图片数量'] = '无法获取'
# 获取点赞数
like = re.search(r'<meta name="og:xhs:note_like" content="(.*?)">',html,re.S)
if not like:
my_data_dict['点赞数'] = '无法获取'
else:
my_data_dict['点赞数'] = like.group(1)
# 获取收藏数
collect = re.search(r'<meta name="og:xhs:note_collect" content="(.*?)">',html,re.S)
if not collect:
my_data_dict['收藏数'] = '无法获取'
else:
my_data_dict['收藏数'] = collect.group(1)
# 获取评论数
comment = re.search(r'<meta name="og:xhs:note_comment" content="(.*?)">',html,re.S)
if not comment:
my_data_dict['评论数'] = '无法获取'
else:
my_data_dict['评论数'] = comment.group(1)
print(my_data_dict)
try:
writer.writerow(my_data_dict)
except Exception as e:
print('写入文件失败',e)
else:
print('没有成功获取响应')
except Exception as e:
print(f'没有成功获取响应,错误内容为:{e}')
except Exception as e:
print('处理数据发送错误,',e)
except Exception as e:
print('打开文件失败',e)