python爬虫实战——小红书

news2025/1/11 14:42:48

目录

1、博主页面分析

2、在控制台预先获取所有作品页的URL 

3、在 Python 中读入该文件并做准备工作 

4、处理图文类型作品

5、处理视频类型作品

6、异常访问而被中断的现象

7、完整参考代码 


        任务:在 win 环境下,利用 Python、webdriver、JavaScript等,获取 xiaohongshu 某个博主的全部作品。

        本文仅做学习和交流使用。

1、博主页面分析

 

        section 代表每一项作品,但即使博主作品有很多,在未登录状态下,只会显示 20 项左右。向下滚动页面,section 发生改变(个数不变),标签中的 index 会递增。  

 

        向下滚动页面时,到一定的范围时,会发送一个获取作品数据的请求,该请求每次只请求 30 项作品数据。该请求携带了 cookies 以及其他不确定值的参数。 

 

2、在控制台预先获取所有作品页的URL 

        为了获取博主的全部作品数据,在登录的状态下访问目标博主页面,在控制台中注入JavaScript 脚本。该脚本不断滚动页面到最底部,每次滚动一段距离后,都获取每一个作品的信息(通过a标签的 href 获取到作品页URL;通过判断a标签是否有一个 class='play-icon' 的后代元素来判断是图文还是视频类型的作品,如果有该标签就是视频作品,反之则是图文作品)。

        将作品页 URL 作为键,作品是视频作品还是图文作品作为值,添加到 js 对象中。

        显示完所有的作品,即滚动到最底部时,将所有获取到的作品信息导出为 txt 文件。

         在控制台中执行:

// 页面高度
const vh = window.innerHeight
let work_obj = {}

// 延迟
function delay(ms){  
    return new Promise(resolve => setTimeout(resolve, ms));  
}

async function action() {  
  let last_height = document.body.offsetHeight;
  window.scrollTo(0, window.scrollY + vh * 1.5)
  
  ul = 	document.querySelector('#userPostedFeeds').querySelectorAll('.cover')

  ul.forEach((e,index)=>{
    // length 为 0 时是图片,为 1 时为视频
    work_obj[e.href] = ul[index].querySelector('.play-icon') ? 1 : 0
  })
	// 延迟500ms
  await delay(500);
  // console.log(last_height, document.body.offsetHeight)
  
  // 判断是否滚动到底部
  if(document.body.offsetHeight > last_height){
    action()
  }else{
    console.log('end')
    // 作品的数量
    console.log(Object.keys(work_obj).length)

    // 转换格式,并下载为txt文件
    var content = JSON.stringify(work_obj);  
    var blob = new Blob([content], {type: "text/plain;charset=utf-8"});  
    var link = document.createElement("a");  
    link.href = URL.createObjectURL(blob);  
    link.download = "xhs_works.txt";  
    link.click();
  }
}

action()

        写出的 txt 文件内容如下:

 

3、在 Python 中读入该文件并做准备工作 

# 获取当前时间
def get_current_time():
    now = datetime.now()
    format_time = now.strftime("_%Y-%m-%d__%H-%M-%S-%f__")
    return format_time

# 下载的作品保存的路径,以作者主页的 id 号命名
ABS_BASE_URL = f'G:\\639476c10000000026006023'

# 检查作品是否已经下载过
def check_download_or_not(work_id, is_pictures):
    end_str = 'pictures' if is_pictures else 'video'
    # work_id 是每一个作品的目录,检查目录是否存在并且是否有内容,则能判断对应的作品是否被下载过
    path = f'{ABS_BASE_URL}/{work_id}-{end_str}'
    if os.path.exists(path) and os.path.isdir(path):
        if os.listdir(path):
            return True
    return False

# 下载资源
def download_resource(url, save_path):
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        with open(save_path, 'wb') as file:
            for chunk in response.iter_content(1024):
                file.write(chunk)

读入文件,判断作品数量然后进行任务分配: 

# 读入文件
content = ''
with open('./xhs_works.txt', mode='r', encoding='utf-8') as f:
    content = json.load(f)

# 转换成 [[href, is_pictures],[href, is_pictures],...] 类型
# 每一维中分别是作品页的URL、作品类型
url_list = [list(pair) for pair in content.items()]

# 有多少个作品
length = len(url_list)

if length > 3:
    ul = [url_list[0: int(length / 3) + 1], url_list[int(length / 3) + 1: int(length / 3) * 2 + 1],url_list[int(length / 3) * 2 + 1: length]]
    # 开启三个线程并分配任务
    for child_ul in ul:
        thread = threading.Thread(target=thread_task, args=(child_ul,))
        thread.start()
else:
    thread_task(url_list)

若使用多线程,每一个线程处理自己被分配到的作品列表: 

# 每一个线程遍历自己分配到的作品列表,进行逐项处理
def thread_task(ul):
    for item in ul:
        href = item[0]
        is_pictures = (True if item[1] == 0 else False)
        res = work_task(href, is_pictures)
        if res == 0:        # 被阻止正常访问
            break

处理每一项作品: 

# 处理每一项作品
def work_task(href, is_pictures):
    # href 中最后的一个路径参数就是博主的id
    work_id = href.split('/')[-1]

    # 判断是否已经下载过该作品
    has_downloaded = check_download_or_not(work_id, is_pictures)

    # 没有下载,则去下载
    if not has_downloaded:
        if not is_pictures:
            res = deal_video(work_id)
        else:
            res = deal_pictures(work_id)
        if res == 0:
            return 0            # 无法正常访问
    else:
        print('当前作品已被下载')
        return 2
    return 1

 

4、处理图文类型作品

        对于图文类型,每一张图片都作为 div 元素的背景图片进行展示,图片对应的 URL 在 div 元素的 style 中。 可以先获取到 style 的内容,然后根据圆括号进行分隔,最后得到图片的地址。

        这里拿到的图片是没有水印的。

# 处理图片类型作品的一系列操作
def download_pictures_prepare(res_links, path, date):
    # 下载作品到目录
    index = 0
    for src in res_links:
        download_resource(src, f'{path}/{date}-{index}.webp')
        index += 1

# 处理图片类型的作品
def deal_pictures(work_id):
    # 直接 requests 请求回来,style 是空的,使用 webdriver 获取当前界面的源代码
    temp_driver = webdriver.Chrome()
    temp_driver.set_page_load_timeout(5)
    temp_driver.get(f'https://www.xiaohongshu.com/explore/{work_id}')
    sleep(1)
    try:
        # 如果页面中有 class='feedback-btn' 这个元素,则表示不能正常访问
        temp_driver.find_element(By.CLASS_NAME, 'feedback-btn')
    except NoSuchElementException:        # 没有该元素,则说明能正常访问到作品页面
        WebDriverWait(temp_driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'swiper-wrapper')))
        
        # 获取页面的源代码
        source_code = temp_driver.page_source
        temp_driver.quit()

        html = BeautifulSoup(source_code, 'lxml')
        swiper_sliders = html.find_all(class_='swiper-slide')
        # 当前作品的发表日期
        date = html.find(class_='bottom-container').span.string.split(' ')[0].strip()

        # 图片路径
        res_links = []
        for item in swiper_sliders:
            # 在 style 中提取出图片的 url
            url = item['style'].split('url(')[1].split(')')[0].replace('"', '').replace('"', '')
            if url not in res_links:
                res_links.append(url)

        #为图片集创建目录
        path = f'{ABS_BASE_URL}/{work_id}-pictures'
        try:
            os.makedirs(path)
        except FileExistsError:
            # 目录已经存在,则直接下载到该目录下
            download_pictures_prepare(res_links, path, date)
        except Exception as err:
            print(f'deal_pictures 捕获到其他错误:{err}')
        else:
            download_pictures_prepare(res_links, path, date)
        finally:
            return 1
    except Exception as err:
        print(f'下载图片类型作品 捕获到错误:{err}')
        return 1
    else:
        print(f'访问作品页面被阻断,下次再试')
        return 0

 

5、处理视频类型作品

        获取到的视频有水印。 

# 处理视频类型的作品
def deal_video(work_id):
    temp_driver = webdriver.Chrome()
    temp_driver.set_page_load_timeout(5)
    temp_driver.get(f'https://www.xiaohongshu.com/explore/{work_id}')
    sleep(1)
    try:
        temp_driver.find_element(By.CLASS_NAME, 'feedback-btn')
    except NoSuchElementException:
        WebDriverWait(temp_driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'player-container')))
        source_code = temp_driver.page_source
        temp_driver.quit()

        html = BeautifulSoup(source_code, 'lxml')
        video_src = html.find(class_='player-el').video['src']
        # 作品发布日期
        date = html.find(class_='bottom-container').span.string.split(' ')[0].strip()

        # 为视频作品创建目录,以 作品的id号 + video 命名目录
        path = f'{ABS_BASE_URL}/{work_id}-video'
        try:
            os.makedirs(path)
        except FileExistsError:
            download_resource(video_src, f'{path}/{date}.mp4')
        except Exception as err:
            print(f'deal_video 捕获到其他错误:{err}')
        else:
            download_resource(video_src, f'{path}/{date}.mp4')
        finally:
            return 1
    except Exception as err:
        print(f'下载视频类型作品 捕获到错误:{err}')
        return 1
    else:
        print(f'访问视频作品界面被阻断,下次再试')
        return 0

 

 

6、异常访问而被中断的现象

         频繁的访问和下载资源会被重定向到如下的页面,可以通过获取到该页面的特殊标签来判断是否被重定向连接,如果是,则及时中断访问,稍后再继续。

        使用 webdriver 访问页面,页面打开后,在 try 中查找是否有 class='feedback-btn' 元素(即下方的 我要反馈 的按钮)。如果有该元素,则在 else 中进行提示并返回错误码退出任务。如果找不到元素,则会触发 NoSuchElementException 的错误,在 except 中继续任务即可。

    try:
        temp_driver.find_element(By.CLASS_NAME, 'feedback-btn')
    except NoSuchElementException:
        # 正常访问到作品页面
        pass
    except Exception as err:
        # 其他的异常
        return 1
    else:
        # 不能访问到作品页面
        return 0

 

7、完整参考代码 


import json
import threading
import requests,os
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from datetime import datetime
from selenium import webdriver
from time import sleep
from bs4 import BeautifulSoup

# 获取当前时间
def get_current_time():
    now = datetime.now()
    format_time = now.strftime("_%Y-%m-%d__%H-%M-%S-%f__")
    return format_time

# 下载的作品保存的路径,以作者主页的 id 号命名
ABS_BASE_URL = f'G:\\639476c10000000026006023'

# 检查作品是否已经下载过
def check_download_or_not(work_id, is_pictures):
    end_str = 'pictures' if is_pictures else 'video'
    # work_id 是每一个作品的目录,检查目录是否存在并且是否有内容,则能判断对应的作品是否被下载过
    path = f'{ABS_BASE_URL}/{work_id}-{end_str}'
    if os.path.exists(path) and os.path.isdir(path):
        if os.listdir(path):
            return True
    return False

# 下载资源
def download_resource(url, save_path):
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        with open(save_path, 'wb') as file:
            for chunk in response.iter_content(1024):
                file.write(chunk)

# 处理图片类型作品的一系列操作
def download_pictures_prepare(res_links, path, date):
    # 下载作品到目录
    index = 0
    for src in res_links:
        download_resource(src, f'{path}/{date}-{index}.webp')
        index += 1

# 处理图片类型的作品
def deal_pictures(work_id):
    # 直接 requests 请求回来,style 是空的,使用 webdriver 获取当前界面的源代码
    temp_driver = webdriver.Chrome()
    temp_driver.set_page_load_timeout(5)
    temp_driver.get(f'https://www.xiaohongshu.com/explore/{work_id}')
    sleep(1)
    try:
        temp_driver.find_element(By.CLASS_NAME, 'feedback-btn')
    except NoSuchElementException:
        WebDriverWait(temp_driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'swiper-wrapper')))
        source_code = temp_driver.page_source
        temp_driver.quit()

        html = BeautifulSoup(source_code, 'lxml')
        swiper_sliders = html.find_all(class_='swiper-slide')
        # 当前作品的发表日期
        date = html.find(class_='bottom-container').span.string.split(' ')[0].strip()

        # 图片路径
        res_links = []
        for item in swiper_sliders:
            url = item['style'].split('url(')[1].split(')')[0].replace('"', '').replace('"', '')
            if url not in res_links:
                res_links.append(url)

        #为图片集创建目录
        path = f'{ABS_BASE_URL}/{work_id}-pictures'
        try:
            os.makedirs(path)
        except FileExistsError:
            # 目录已经存在,则直接下载到该目录下
            download_pictures_prepare(res_links, path, date)
        except Exception as err:
            print(f'deal_pictures 捕获到其他错误:{err}')
        else:
            download_pictures_prepare(res_links, path, date)
        finally:
            return 1
    except Exception as err:
        print(f'下载图片类型作品 捕获到错误:{err}')
        return 1
    else:
        print(f'访问作品页面被阻断,下次再试')
        return 0


# 处理视频类型的作品
def deal_video(work_id):
    temp_driver = webdriver.Chrome()
    temp_driver.set_page_load_timeout(5)
    temp_driver.get(f'https://www.xiaohongshu.com/explore/{work_id}')
    sleep(1)
    try:
        # 访问不到正常内容的标准元素
        temp_driver.find_element(By.CLASS_NAME, 'feedback-btn')
    except NoSuchElementException:
        WebDriverWait(temp_driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'player-container')))
        source_code = temp_driver.page_source
        temp_driver.quit()

        html = BeautifulSoup(source_code, 'lxml')
        video_src = html.find(class_='player-el').video['src']
        # 作品发布日期
        date = html.find(class_='bottom-container').span.string.split(' ')[0].strip()

        # 为视频作品创建目录
        path = f'{ABS_BASE_URL}/{work_id}-video'
        try:
            os.makedirs(path)
        except FileExistsError:
            download_resource(video_src, f'{path}/{date}.mp4')
        except Exception as err:
            print(f'deal_video 捕获到其他错误:{err}')
        else:
            download_resource(video_src, f'{path}/{date}.mp4')
        finally:
            return 1
    except Exception as err:
        print(f'下载视频类型作品 捕获到错误:{err}')
        return 1
    else:
        print(f'访问视频作品界面被阻断,下次再试')
        return 0

# 检查作品是否已经下载,如果没有下载则去下载
def work_task(href, is_pictures):
    work_id = href.split('/')[-1]
    has_downloaded = check_download_or_not(work_id, is_pictures)
    # 没有下载,则去下载
    if not has_downloaded:
        if not is_pictures:
            res = deal_video(work_id)
        else:
            res = deal_pictures(work_id)
        if res == 0:
            return 0            # 受到阻断
    else:
        print('当前作品已被下载')
        return 2
    return 1

def thread_task(ul):
    for item in ul:
        href = item[0]
        is_pictures = (True if item[1] == 0 else False)
        res = work_task(href, is_pictures)
        if res == 0:        # 被阻止正常访问
            break

if __name__ == '__main__':
    content = ''
    with open('xhs_works.txt', mode='r', encoding='utf-8') as f:
        content = json.load(f)
    url_list = [list(pair) for pair in content.items()]
    length = len(url_list)
    if length > 3:
        ul = [url_list[0: int(length / 3) + 1], url_list[int(length / 3) + 1: int(length / 3) * 2 + 1],
              url_list[int(length / 3) * 2 + 1: length]]
        for child_ul in ul:
            thread = threading.Thread(target=thread_task, args=(child_ul,))
            thread.start()
    else:
        thread_task(url_list)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1514560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

让手机变相机,微单变全画幅的外设-斯莫格摄影套装开箱

大家好,我是Circaboy,近期给我的手机和相机入了一整套专业的摄影外设套装,然后我发现专业设备加持下的摄影着实是比我之前的要好很多,所以我就写了个文章做个简单的介绍和专业外设加持下的摄影对比。 本次入手的是斯莫格摄影套装…

Linux学习(4)——使用编辑器

1.gedit编辑器 简单易懂,依赖图形界面。可以使用ctrlc ctrlv等快捷键,ctrls进行保存,与windows系统中相类似。 2.vi/vim编辑器 vi/vim可以直接通过控制台的终端完成文本的编辑,不依赖图形界面,使用范围更广。它的编辑…

cesiumlab中shp转3dtiles白模效果一

安装cesiumlab 如果没有安装cesiumlab,去官网下载安装一个即可 http://www.cesiumlab.com/cesiumlab.html 效果 步骤 1、准备shp面数据 2、打开cesiumlab软件转换 选择shp面数据 设置高度,如果shp面中有高度字段,可以用高度字段&#xff…

ASP.NET排课实验室排课,生成班级课表实验室课表教师课表(vb.net)-214-(代码+说明)

转载地址: http://www.3q2008.com/soft/search.asp?keyword214 要看成品演示 请联系客服发给您成品演示 课题:实验课排课系统 计算机 上机课 一周上5天课,周一到周五 一周上5天课,周一到周五 因为我排的是实验课,最好1&#xf…

javaweb day16 mysql

mysql 安装: 企业开发使用方法 安装虚拟机代替服务器 数据模型 创建数据库 写法 sql简介

【考研数学】打基础用张宇《30讲》还是武忠祥《基础篇》?

基础课不太可能所有的东西全都覆盖,还是先搭起一个知识框架,然后不断的填充和完善。 所以不必太过于在意少一些东西,我们不可能一口吃成胖子,基础知识肯定不会遗漏的,只可能一些技巧不到位。 从自己的情况考虑&#…

HTTP压测工具wrk安装与使用

一、前言 wrk是一个基于C语言开发的用于HTTP性能测试的开源工具,它可以模拟多个并发连接,测量服务器的响应时间和吞吐量,并且会给出较为全面的测试结果 1、本文主要内容 在Windows、macOS、Linux(CentOS & Ubuntu等&#xff…

使用ChatGPT高效完成简历制作[中篇]-有爱AI实战教程(五)

演示站点: https://ai.uaai.cn 对话模块 官方论坛: www.jingyuai.com 京娱AI 导读:在使用 ChatGPT 时,当你给的指令越精确,它的回答会越到位,举例来说,假如你要请它帮忙写文案,如果没…

上海亚商投顾:沪指震荡调整 飞行汽车概念股持续爆发

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 沪指昨日震荡调整,深成指走势稍强,创业板指一度涨超1%,黄白二线走势分化&a…

Nginx怎么去做负载均衡?

一.什么是负载均衡? 负载均衡是一种在计算机网络中分配工作负载的技术,旨在将请求或任务均匀地分配给多个服务器、计算资源或其他设备,以避免单个节点过载,并提高系统的可靠性、稳定性和性能。负载均衡通常用于分布式系统、网络服…

QT网络编程之实现TCP客户端和服务端

一.QT5.12实现TCP客户端和服务端功能 1.QT中实现TCP通信主要用到了以下类:QTcpServer、QTcpSocket、QHostAddress 2.基本流程: 使用QTcpServer来创建一个TCP服务器,在新的连接建立时,将新建立连接的socket添加到列表中&#xf…

Copilot如何将word文稿一键转为PPT

背景 很多小伙伴平时经常会遇到的一个场景是,如何将word文稿图文转为PPT。 这个过程是既复杂而又无趣的。 现在,有了copilot,你可以一键搞定! 使用copilot Pro来实现 比如我们想要做一个关于copilot studio的PPT展示&#xf…

使用tui-image-editor 图片编辑 标注图片

需求背景: 鼠标悬浮在图片上 出现编辑按钮 点击编辑 对该图片进行编辑(输入文案、涂鸦、标记、裁剪等) 可以体验一下它线上编辑器 Image-editor | TOAST UI :: Make Your Web Delicious! 使用 首先在你的前端项目中安装: np…

Spring Boot 中的 Sleuth 详解

Spring Boot 中的 Sleuth 是一个用于分布式追踪的库,它可以帮助你追踪和理解分布式系统中的请求如何跨越多个服务和网络调用。通过使用 Sleuth,你可以收集关于请求路径、延迟、异常等的信息,从而更容易地诊断问题并进行性能优化。 一、下面是…

chown: changing ownership of ‘.‘: Permission denied 的一种解法

前言 最近在新电脑用 colima docker 启动服务遇到了这样的报错 chown: changing ownership of .: Permission denied在网上搜索了很久,不管是google还是stack overflow都没有突破口,只要绑定了 volumes 就会报错,按照网上说的方法&#xff…

分布式协议笔记

目录 一致性协议 2pc 3pc CanCommit PreCommit doCommit 回滚 3PC的优点和缺陷 paxos算法 一、Paxos算法背景 二、Paxos算法流程 Paxos算法实例1 Paxos算法实例2 Paxos算法实例3 三、Multi-Paxos算法 附Paxos算法推导过程 raft 概念介绍 算法步骤 Leader选…

css实现高度是宽度一半的效果

1、方法一&#xff1a;使用变量:root、var()、clac()实现&#xff1a; 1.1 效果如下&#xff1a; 2.2 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>&l…

AMEYA360:稳先微汽车驱动芯片—智能高边开关WS7系列

近几年&#xff0c;新能源汽车高速发展&#xff0c;用车浪潮蔓延全球&#xff0c;我国新能源汽车占有量连续9年居全球前列&#xff0c;2023年全年市占率达37.7%&#xff0c;市场规模可观&#xff0c;并显现出以下特点&#xff1a;电车产品对比油车优势明显、消费者接受度高、市…

KNN算法对鸢尾花进行分类:添加网格搜索和交叉验证

优化——添加网格搜索和交叉验证 from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection imp…

DevOps-SonarQube整合Jenkins

下载SonarQube Scanner 登录Jenkins服务器&#xff0c;下载SonarQube Scanner wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip安装unzip&#xff0c;需要通过它来解压zip压缩包 yum install -y unzip解压So…