Python爬虫 - 豆瓣图书数据爬取、处理与存储

文章目录

  • 前言
  • 一、使用版本
  • 二、需求分析
    • 1. 分析要爬取的内容
      • 1.1 分析要爬取的单个图书信息
      • 1.2 爬取步骤
        • 1.2.1 爬取豆瓣图书标签分类页面
        • 1.2.2 爬取分类页面
        • 1.2.3 爬取单个图书页面
      • 1.3 内容所在的标签定位
    • 2. 数据用途
      • 2.1 基础分析
      • 2.2 高级分析
    • 3. 应对反爬机制的策略
      • 3.1 使用 `User-Agent` 模拟真实浏览器请求
      • 3.2 实施随机延时策略
      • 3.3 构建和使用代理池
      • 3.4 其他
  • 三、编写爬虫代码
    • 1. 爬取标签分类html
    • 2. 爬取单个分类的所有页面
    • 3. 爬取单个图书的html
  • 四、数据处理与存储
    • 1. 解析html并把数据保存到csv文件
      • 1.1 字段说明
      • 1.2 代码实现
    • 2. 数据清洗与存储
      • 2.1 数据清洗
      • 2.2 数据存储
        • 2.2.1 表设计
        • 2.2.2 表实现
      • 2.3 代码实现


前言

在数字化时代,网络爬虫技术为我们提供了强大的数据获取能力,使得从各类网站提取信息变得更加高效和便捷。豆瓣读书作为一个广受欢迎的图书评价和推荐平台,汇聚了大量的书籍信息,包括书名、作者、出版社、评分等。这些信息不仅对读者选择图书有帮助,也为出版商和研究人员提供了宝贵的数据资源。

本项目旨在通过 Python 爬虫技术,系统性地抓取豆瓣读书网站上的图书信息,并将其存储为结构化的数据格式,以便后续分析和研究。我们将使用 requests 和 BeautifulSoup 库进行网页请求和数据解析,利用 pandas 进行数据处理,最后将清洗后的数据存储到 MySQL 数据库中。


一、使用版本

pythonrequestsbs4beautifulsoup4soupsievelxmlpandassqlalchemymysql-connector-pythonselenium
版本3.8.52.31.00.0.24.12.32.64.9.32.0.32.0.369.0.04.15.2

二、需求分析

1. 分析要爬取的内容

1.1 分析要爬取的单个图书信息

点击进入豆瓣读书官网:https://book.douban.com/

随便点开一本图书

在这里插入图片描述

如下图,在图书首页可以看到标题、作者、出版社、出版日期、页数、价格和评分等信息。那我们的目的就是要把这些信息爬取下来保存到csv文件中作为原始数据

在这里插入图片描述

鼠标右击,选择检查,找到相关信息的网页源码。

在这里插入图片描述

当鼠标悬浮在如下图红色箭头所指的标签上之后,我们发现左侧我们想要爬取的信息范围被显示出来,说明我们要爬取的单个图书信息内容就在该标签中。

在这里插入图片描述

复制了该标签的内容如下图所示,从该标签中可以看到需要爬取的信息都有。

我们的目的就是把单个图书的hmtl文件爬取下来,然后使用BeautifulSoup解析后把数据保存到csv文件中。

<div class="subjectwrap clearfix">
<div class="subject clearfix">
<div id="mainpic" class=""><a class="nbg" href="https://img1.doubanio.com/view/subject/l/public/s34971089.jpg" title="再造乡土"><img src="https://img1.doubanio.com/view/subject/s/public/s34971089.jpg" title="点击看大图" alt="再造乡土" rel="v:photo" style="max-width: 135px;max-height: 200px;"></a>
</div>
<div id="info" class=""><span><span class="pl"> 作者</span>:<a class="" href="/author/4639586">(美)萨拉·法默</a></span><br><span class="pl">出版社:</span><a href="https://book.douban.com/press/2476">广西师范大学出版社</a><br><span class="pl">出品方:</span><a href="https://book.douban.com/producers/795">望mountain</a><br><span class="pl">副标题:</span> 1945年后法国农村社会的衰落与重生<br><span class="pl">原作名:</span> Rural Inventions: The French Countryside after 1945<br><span><span class="pl"> 译者</span>:<a class="" href="/search/%E5%8F%B6%E8%97%8F">叶藏</a></span><br><span class="pl">出版年:</span> 2024-11<br><span class="pl">页数:</span> 288<br><span class="pl">定价:</span> 79.20元<br><span class="pl">装帧:</span> 精装<br><span class="pl">ISBN:</span> 9787559874597<br>
</div>
</div>
<div id="interest_sectl" class=""><div class="rating_wrap clearbox" rel="v:rating"><div class="rating_logo">豆瓣评分</div><div class="rating_self clearfix" typeof="v:Rating"><strong class="ll rating_num " property="v:average"> 8.5 </strong><span property="v:best" content="10.0"></span><div class="rating_right "><div class="ll bigstar45"></div><div class="rating_sum"><span class=""><a href="comments" class="rating_people"><span property="v:votes">55</span>人评价</a></span></div></div></div>
<span class="stars5 starstop" title="力荐">5星
</span>
<div class="power" style="width:37px"></div><span class="rating_per">29.1%</span><br>
<span class="stars4 starstop" title="推荐">4星
</span>
<div class="power" style="width:64px"></div><span class="rating_per">49.1%</span><br>
<span class="stars3 starstop" title="还行">3星
</span>
<div class="power" style="width:26px"></div><span class="rating_per">20.0%</span><br>
<span class="stars2 starstop" title="较差">2星
</span>
<div class="power" style="width:2px"></div><span class="rating_per">1.8%</span><br>
<span class="stars1 starstop" title="很差">1星
</span>
<div class="power" style="width:0px"></div><span class="rating_per">0.0%</span><br></div>
</div>
</div>

1.2 爬取步骤

1.2.1 爬取豆瓣图书标签分类页面

豆瓣图书标签分类地址:https://book.douban.com/tag/?view=type&icn=index-sorttags-all

爬取图书标签分类页面保存为../douban/douban_book/douban_book_tag/douban_book_all_tag.html文件。然后使用BeautifulSoup解析../douban/douban_book/douban_book_tag/douban_book_all_tag.html文件,获取每个分类标签的名称和链接

在这里插入图片描述

1.2.2 爬取分类页面

例如,点进小说标签后的页面如下:
可以看到访问的网址是https://book.douban.com/tag/小说,由此可以推断不同分类标签第一页的网址是https://book.douban.com/tag/标签名称

在这里插入图片描述
在这里插入图片描述

在上面的两个页面中可以看到每一页显示了多个小说的大概信息(这些信息并不能满足我的爬取要求),那我就需要获取每个分页的链接,然后根据每个分页的链接保存每一页的html文件。

如下图所示,检查后发现每一页是20条数据,而且带有两个参数(starttype;start表示每页开始位置,每页20条数据),由此可以推断每一页的链接为:https://book.douban.com/tag/<标签名称>?start=<20的倍数>&type=T。然后从每一页中解析出每个图书的链接

在这里插入图片描述

1.2.3 爬取单个图书页面

获得每个图书的链接后,就可以根据链接保存每个图书的html文件。然后就可以使用BeautifulSoup从该页面中解析出图书的信息。

单个图书的页面如下图所示:

在这里插入图片描述

1.3 内容所在的标签定位

可以使用CSS选择器定位需要爬取的内容所在的标签位置。
示例:标题标签定位
鼠标右击标题部分,选择检查,显示出标题部分的源码之后;右击有标题的源码,点击复制,选择复制selector

在这里插入图片描述

复制后的selector如下:

#wrapper > h1 > span

2. 数据用途

2.1 基础分析

  1. 描述性统计

    • 计算书籍价格、页数等数值型字段的平均值、中位数、最大值、最小值以及标准差。
    • 统计不同装帧类型(binding)或出版社(publisher)的书籍数量。
  2. 频率分布

    • 制作出版年份(publication_year)的频率分布图,观察每年的出版趋势。
    • 分析各星级评分(stars5_starstop至stars1_starstop)所占的比例,了解整体评分分布情况。
  3. 简单关系探索

    • 探索书籍价格与评分之间的简单相关性。
    • 研究书籍页数与评分的关系,看是否有明显的关联。
  4. 分类汇总

    • 按作者(author)、出版社(publisher)或者丛书系列(series)对书籍进行分组,并计算每组的平均评分、总销量等指标。

2.2 高级分析

  1. 预测建模

    • 使用机器学习算法预测一本书的可能评分,基于如作者、出版社、价格、出版年份等因素。
    • 构建模型预测书籍销售量,帮助出版社或书店优化库存管理。
  2. 聚类分析

    • 对书籍进行聚类,找出具有相似特征的书籍群体,例如相似的主题、读者群体或市场表现。
    • 根据用户评论链接中的文本信息进行主题建模,以识别常见的读者关注点或反馈类型。
  3. 因果分析

    • 通过控制其他变量,研究特定因素(如封面设计、翻译质量等)对书籍评分或销量的影响。
    • 使用实验设计或准实验方法评估营销活动对书籍销量的影响。
  4. 时间序列分析

    • 如果有连续多年的数据,可以对出版年份和销量等进行时间序列分析,预测未来的趋势。
    • 研究特定事件(如作者获得奖项)对书籍销量的时间影响。
  5. 网络分析

    • 构建作者合作网络或书籍引用网络,探索学术或文学领域内的合作模式和影响力传播。
  6. 情感分析

    • 对用户评论链接指向的内容进行情感分析,理解读者对书籍的情感倾向。
  7. 多变量回归分析

    • 研究多个变量(如价格、页数、出版年份等)如何共同影响一本书的评分或销量。

3. 应对反爬机制的策略

3.1 使用 User-Agent 模拟真实浏览器请求

许多网站通过检查HTTP请求头中的 User-Agent 字段来判断请求是否来自真实的浏览器。默认情况下,Python库发送的请求可能带有明显的标识,容易被识别为自动化工具。因此,修改 User-Agent 来模拟不同的浏览器和操作系统可以有效地绕过这一检测。

3.2 实施随机延时策略

频繁且规律性的请求频率是典型的爬虫行为特征之一。通过在每次请求之间加入随机延迟,不仅可以模仿人类用户的访问模式,还能减少服务器负载,降低被封禁的风险。

3.3 构建和使用代理池

直接从同一个IP地址发起大量请求容易引起封禁。通过构建并使用代理池,您可以轮换不同的IP地址来进行请求,从而分散风险。这不仅增加了爬虫的隐蔽性,也减轻了单个IP地址的压力。

3.4 其他

  • 验证码处理:某些网站可能还会使用验证码来验证用户身份。针对这种情况,可以考虑使用第三方OCR服务或专门的验证码识别API。
  • JavaScript渲染页面:部分现代网站依赖JavaScript动态加载内容,普通的HTML解析可能无法获取完整数据。这时可以使用像Selenium这样的工具,它能启动一个真实的浏览器实例执行JavaScript。

三、编写爬虫代码

1. 爬取标签分类html

页面如下图所示:

在这里插入图片描述

代码实现:

import random
import time
from pathlib import Pathimport requestsdef get_request(url, **kwargs):time.sleep(random.uniform(0.1, 2))print(f'===============================地址:{url} ===============================')# 定义一组User-Agent字符串user_agents = [# Chrome'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',# Firefox'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0','Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0','Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/117.0',# Edge'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2040.0',# Safari'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15',]# 请求头headers = {'User-Agent': random.choice(user_agents)}# 用户名密码认证(私密代理/独享代理)username = ""password = ""proxies = {"http": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,"proxy": '36.25.243.5:11768'},"https": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,"proxy": '36.25.243.5:11768'}}max_retries = 3for attempt in range(max_retries):try:response = requests.get(url=url, timeout=10, headers=headers, **kwargs)# response = requests.get(url=url, timeout=10, headers=headers, proxies=proxies, **kwargs)if response.status_code == 200:return responseelse:print(f"请求失败,状态码: {response.status_code},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")except requests.exceptions.RequestException as e:print(f"请求过程中发生异常: {e},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")# 如果不是最后一次尝试,则等待一段时间再重试if attempt < max_retries - 1:time.sleep(random.uniform(1, 2))print('================多次请求失败,请查看异常情况================')return None  # 或者返回最后一次的响应,取决于你的需求def save_book_html_file(save_dir, file_name, content):dir_path = Path(save_dir)# 确保保存目录存在,如果不存在则创建所有必要的父级目录dir_path.mkdir(parents=True, exist_ok=True)# 使用 'with' 语句打开文件以确保正确关闭文件流with open(save_dir + file_name, 'w', encoding='utf-8') as fp:print(f"==============================={save_dir + file_name} 文件已保存===============================")fp.write(str(content))def download_book_tag():save_dir = '../douban/douban_book/douban_book_tag/'file_name = 'douban_book_all_tag.html'book_tag_url = 'https://book.douban.com/tag/?view=type&icn=index-sorttags-all'tag_file_path = Path(save_dir + file_name)if tag_file_path.exists() and tag_file_path.is_file():print(f'\n===============================文件 {tag_file_path} 已存在===============================')else:print(f'===============================文件 {tag_file_path} 不存在,正在下载...===============================')save_book_html_file(save_dir=save_dir, file_name=file_name, content=get_request(book_tag_url).text)if __name__ == '__main__':download_book_tag()

运行结果如下图所示:

在这里插入图片描述

该代码可以重复执行,重复执行会自动检查文件是否已下载,如下图所示:

在这里插入图片描述

保存后的文件如下图:

在这里插入图片描述

2. 爬取单个分类的所有页面

基于上面的爬取标签分类继续实现的代码,使用BeautifulSoup解析标签分类html后,根据获取的标签分类名称和链接循环获取每个分类下的所有html页面。

import random
import time
from pathlib import Pathimport requests
from bs4 import BeautifulSoup# 快代理试用:https://www.kuaidaili.com/freetest/def get_request(url, **kwargs):time.sleep(random.uniform(0.1, 2))print(f'===============================地址:{url} ===============================')# 定义一组User-Agent字符串user_agents = [# Chrome'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',# Firefox'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0','Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0','Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/117.0',# Edge'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2040.0',# Safari'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15',]# 请求头headers = {'User-Agent': random.choice(user_agents)}# 用户名密码认证(私密代理/独享代理)username = "17687015657"password = "qvbgms8w"proxies = {"http": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,"proxy": '36.25.243.5:11768'},"https": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,"proxy": '36.25.243.5:11768'}}max_retries = 3for attempt in range(max_retries):try:response = requests.get(url=url, timeout=10, headers=headers, **kwargs)# response = requests.get(url=url, timeout=10, headers=headers, proxies=proxies, **kwargs)if response.status_code == 200:return responseelse:print(f"请求失败,状态码: {response.status_code},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")except requests.exceptions.RequestException as e:print(f"请求过程中发生异常: {e},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")# 如果不是最后一次尝试,则等待一段时间再重试if attempt < max_retries - 1:time.sleep(random.uniform(1, 2))print('================多次请求失败,请查看异常情况================')return None  # 或者返回最后一次的响应,取决于你的需求def save_book_html_file(save_dir, file_name, content):dir_path = Path(save_dir)# 确保保存目录存在,如果不存在则创建所有必要的父级目录dir_path.mkdir(parents=True, exist_ok=True)# 使用 'with' 语句打开文件以确保正确关闭文件流with open(save_dir + file_name, 'w', encoding='utf-8') as fp:print(f"==============================={save_dir + file_name} 文件已保存===============================")fp.write(str(content))def download_book_tag():save_dir = '../douban/douban_book/douban_book_tag/'file_name = 'douban_book_all_tag.html'book_tag_url = 'https://book.douban.com/tag/?view=type&icn=index-sorttags-all'tag_file_path = Path(save_dir + file_name)if tag_file_path.exists() and tag_file_path.is_file():print(f'\n===============================文件 {tag_file_path} 已存在===============================')else:print(f'===============================文件 {tag_file_path} 不存在,正在下载...===============================')save_book_html_file(save_dir=save_dir, file_name=file_name, content=get_request(book_tag_url).text)def get_soup(markup):return BeautifulSoup(markup=markup, features='lxml')def get_book_type_and_href():# 定义HTML文件路径file = '../douban/douban_book/douban_book_tag/douban_book_all_tag.html'# 初始化一个空字典用于存储标签名称和对应的链接name_href_result = {}# 定义豆瓣书籍的基础URL,用于拼接完整的链接book_base_url = 'https://book.douban.com'# 打开并读取HTML文件内容with open(file=file, mode='r', encoding='utf-8') as fp:# 使用BeautifulSoup解析HTML内容soup = get_soup(fp)# 选择包含所有标签链接的主要容器tag = soup.select_one('#content > div > div.article > div:nth-child(2)')# 选择所有包含标签链接的表格行(每个类别下的标签表)tables = tag.select('div > a.tag-title-wrapper + table.tagCol')# 遍历每个表格for table in tables:# 选择表格中的所有行(tr标签)tr_tags = table.select('tr')# 遍历每一行for tr_tag in tr_tags:# 选择行中的所有单元格(td标签)td_tags = tr_tag.select('td')# 遍历每个单元格for td_tag in td_tags:# 选择单元格中的第一个a标签(如果存在)a_tag = td_tag.select_one('a')# 如果找到了a标签,则提取文本和href属性if a_tag:# 提取a标签的文本内容,并去除两端空白字符tag_text = a_tag.string# 获取a标签的href属性,并与基础URL拼接成完整链接tag_href = book_base_url + a_tag.attrs.get('href')# 将提取到的标签文本和链接添加到结果字典中name_href_result[tag_text] = tag_href# 返回包含所有标签名称和对应链接的字典return name_href_resultdef get_book_data_dagai(name, start):book_tag_base_url = 'https://book.douban.com/tag/' + namepayload = {'start': start,'type': 'T'}response = get_request(book_tag_base_url, params=payload)if response is None:return Nonereturn response.textdef download_book_data_dagai(name, start):save_dir = '../douban/douban_book/douban_book_data_dagai/'file_name = f'douban_book_data_dagai_{name}_{start}.html'dagai_file_path = Path(save_dir + file_name)if dagai_file_path.exists() and dagai_file_path.is_file():print(f'===============================文件 {dagai_file_path} 已存在===============================')else:print(f'===============================文件 {dagai_file_path} 不存在,正在下载...===============================')content = get_book_data_dagai(name, start)if content is None:return None# 判断是否是最后一页soup = get_soup(content)p_tag = soup.select_one('#subject_list > p')if p_tag is not None:print(f"===============================分类 {name} 的网页爬取完成===============================")return Truesave_book_html_file(save_dir=save_dir, file_name=file_name, content=content)if __name__ == '__main__':download_book_tag()book_type = get_book_type_and_href()book_type_name = book_type.keys()print(book_type_name)for type_name in book_type_name:print(f'===============================图书分类标签:{type_name}===============================')start_ = 0while True:flag = download_book_data_dagai(type_name, start_)start_ = start_ + 20if flag is None:continueif flag:print(f'======================================图书分类标签 {type_name} 的大概html下载完成======================================')break

执行过程中打印的部分信息如下图所示:

在这里插入图片描述

爬取后保存的部分html文件如下图所示:

在这里插入图片描述

3. 爬取单个图书的html

基于上面的爬取单个分类的所有页面继续实现的代码,使用BeautifulSoup解析每一页的html后,根据获取的单个图书链接获取html页面。

import random
import time
from pathlib import Pathimport requests
from bs4 import BeautifulSoup# 快代理试用:https://www.kuaidaili.com/freetest/def get_request(url, **kwargs):time.sleep(random.uniform(0.1, 2))print(f'===============================地址:{url} ===============================')# 定义一组User-Agent字符串user_agents = [# Chrome'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',# Firefox'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0','Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0','Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/117.0',# Edge'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2040.0',# Safari'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15',]# 请求头headers = {'User-Agent': random.choice(user_agents)}# 用户名密码认证(私密代理/独享代理)username = ""password = ""proxies = {"http": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,"proxy": '36.25.243.5:11768'},"https": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,"proxy": '36.25.243.5:11768'}}max_retries = 3for attempt in range(max_retries):try:response = requests.get(url=url, timeout=10, headers=headers, **kwargs)# response = requests.get(url=url, timeout=10, headers=headers, proxies=proxies, **kwargs)if response.status_code == 200:return responseelse:print(f"请求失败,状态码: {response.status_code},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")except requests.exceptions.RequestException as e:print(f"请求过程中发生异常: {e},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")# 如果不是最后一次尝试,则等待一段时间再重试if attempt < max_retries - 1:time.sleep(random.uniform(1, 2))print('================多次请求失败,请查看异常情况================')return None  # 或者返回最后一次的响应,取决于你的需求def save_book_html_file(save_dir, file_name, content):dir_path = Path(save_dir)# 确保保存目录存在,如果不存在则创建所有必要的父级目录dir_path.mkdir(parents=True, exist_ok=True)# 使用 'with' 语句打开文件以确保正确关闭文件流with open(save_dir + file_name, 'w', encoding='utf-8') as fp:print(f"==============================={save_dir + file_name} 文件已保存===============================")fp.write(str(content))def download_book_tag():save_dir = '../douban/douban_book/douban_book_tag/'file_name = 'douban_book_all_tag.html'book_tag_url = 'https://book.douban.com/tag/?view=type&icn=index-sorttags-all'tag_file_path = Path(save_dir + file_name)if tag_file_path.exists() and tag_file_path.is_file():print(f'\n===============================文件 {tag_file_path} 已存在===============================')else:print(f'===============================文件 {tag_file_path} 不存在,正在下载...===============================')save_book_html_file(save_dir=save_dir, file_name=file_name, content=get_request(book_tag_url).text)def get_soup(markup):return BeautifulSoup(markup=markup, features='lxml')def get_book_type_and_href():# 定义HTML文件路径file = '../douban/douban_book/douban_book_tag/douban_book_all_tag.html'# 初始化一个空字典用于存储标签名称和对应的链接name_href_result = {}# 定义豆瓣书籍的基础URL,用于拼接完整的链接book_base_url = 'https://book.douban.com'# 打开并读取HTML文件内容with open(file=file, mode='r', encoding='utf-8') as fp:# 使用BeautifulSoup解析HTML内容soup = get_soup(fp)# 选择包含所有标签链接的主要容器tag = soup.select_one('#content > div > div.article > div:nth-child(2)')# 选择所有包含标签链接的表格行(每个类别下的标签表)tables = tag.select('div > a.tag-title-wrapper + table.tagCol')# 遍历每个表格for table in tables:# 选择表格中的所有行(tr标签)tr_tags = table.select('tr')# 遍历每一行for tr_tag in tr_tags:# 选择行中的所有单元格(td标签)td_tags = tr_tag.select('td')# 遍历每个单元格for td_tag in td_tags:# 选择单元格中的第一个a标签(如果存在)a_tag = td_tag.select_one('a')# 如果找到了a标签,则提取文本和href属性if a_tag:# 提取a标签的文本内容,并去除两端空白字符tag_text = a_tag.string# 获取a标签的href属性,并与基础URL拼接成完整链接tag_href = book_base_url + a_tag.attrs.get('href')# 将提取到的标签文本和链接添加到结果字典中name_href_result[tag_text] = tag_href# 返回包含所有标签名称和对应链接的字典return name_href_resultdef get_book_data_dagai(name, start):book_tag_base_url = 'https://book.douban.com/tag/' + namepayload = {'start': start,'type': 'T'}response = get_request(book_tag_base_url, params=payload)if response is None:return Nonereturn response.textdef download_book_data_dagai(name, start):save_dir = '../douban/douban_book/douban_book_data_dagai/'file_name = f'douban_book_data_dagai_{name}_{start}.html'dagai_file_path = Path(save_dir + file_name)if dagai_file_path.exists() and dagai_file_path.is_file():print(f'===============================文件 {dagai_file_path} 已存在===============================')else:print(f'===============================文件 {dagai_file_path} 不存在,正在下载...===============================')content = get_book_data_dagai(name, start)if content is None:return None# 判断是否是最后一页soup = get_soup(content)p_tag = soup.select_one('#subject_list > p')if p_tag is not None:print(f"===============================分类 {name} 的网页爬取完成===============================")return Truesave_book_html_file(save_dir=save_dir, file_name=file_name, content=content)def download_book_data_detail():save_dir = '../douban/douban_book/douban_book_data_detail/'dagai_dir = Path('../douban/douban_book/douban_book_data_dagai/')dagai_file_list = dagai_dir.rglob('*.html')for dagai_file in dagai_file_list:soup = get_soup(markup=open(file=dagai_file, mode='r', encoding='utf-8'))a_tag_list = soup.select('#subject_list > ul > li  h2 > a')for a_tag in a_tag_list:href = a_tag.attrs.get('href')book_id = href.split('/')[-2]file_name = f'douban_book_data_detail_{book_id}.html'detail_file_path = Path(save_dir + file_name)if detail_file_path.exists() and detail_file_path.is_file():print(f'===============================文件 {detail_file_path} 已存在===============================')else:print(f'===============================文件 {detail_file_path} 不存在,正在下载...===============================')response = get_request(href)if response is None:continuesave_book_html_file(save_dir, file_name, response.text)def print_in_rows(items, items_per_row=20):for index, name in enumerate(items, start=1):print(f'{name}', end=' ')if index % items_per_row == 0:print()if __name__ == '__main__':download_book_tag()book_type = get_book_type_and_href()book_type_name = book_type.keys()print(book_type_name)for type_name in book_type_name:print(f'===============================图书分类标签:{type_name}===============================')start_ = 0while True:flag = download_book_data_dagai(type_name, start_)start_ = start_ + 20if flag is None:continueif flag:print(f'======================================图书分类标签 {type_name} 的大概html下载完成======================================')breakdownload_book_data_detail()

执行过程中打印的部分信息如下图所示:

在这里插入图片描述

爬取后保存的部分html文件如下图所示:

在这里插入图片描述


四、数据处理与存储

1. 解析html并把数据保存到csv文件

使用BeautifulSouphtml文档中解析出单个图书的信息,循环解析出多个图书数据后,把数据保存到csv文件。

1.1 字段说明

字段名称说明
book_id书籍的唯一标识符。
title书名。
img_src封面图片的网络地址。
author作者姓名。
publisher出版社名称。
producer制作人或出品方(如果有的话)。
original_title原版书名(如果是翻译作品,则为原语言书名)。
translator翻译者姓名(如果有)。
publication_year出版年份。
page_count页数。
price定价。
binding装帧类型(如平装、精装等)。
series丛书系列名称(如果有的话)。
isbn国际标准书号。
rating平均评分。
rating_sum参与评分的人数。
comment_link用户评论链接。
stars5_starstop五星评价所占的比例。
stars4_starstop四星评价所占的比例。
stars3_starstop三星评价所占的比例。
stars2_starstop二星评价所占的比例。
stars1_starstop一星评价所占的比例。

1.2 代码实现

每解析出100条数据,就把解析出的数据保存到csv文件中。

from pathlib import Pathimport pandas as pd
from bs4 import BeautifulSoupdef get_soup(markup):return BeautifulSoup(markup=markup, features='lxml')def parse_detail_html_to_csv():# 定义CSV文件路径csv_file_dir = '../douban/douban_book/data_csv/'csv_file_name = 'douban_books.csv'csv_file_path = Path(csv_file_dir + csv_file_name)csv_file_dir_path = Path(csv_file_dir)csv_file_dir_path.mkdir(parents=True, exist_ok=True)detail_dir = Path('../douban/douban_book/douban_book_data_detail/')detail_file_list = detail_dir.rglob('*.html')book_data = []count = 0for detail_file in detail_file_list:book_id = str(detail_file).split('_')[-1].split('.')[0]soup = get_soup(open(file=detail_file, mode='r', encoding='utf-8'))title = soup.select_one('#wrapper > h1 > span').stringtag_subjectwrap = soup.select_one('#content > div > div.article > div.indent > div.subjectwrap.clearfix')img_src = tag_subjectwrap.select_one('#mainpic > a > img').attrs.get('src')tag_info = tag_subjectwrap.select_one('div.subject.clearfix > #info')tag_author = tag_info.find(name='span', attrs={'class': 'pl'}, string=' 作者')if tag_author is None:author = ''else:author = tag_author.next_sibling.next_sibling.text.strip()tag_publisher = tag_info.find(name='span', attrs={'class': 'pl'}, string='出版社:')if tag_publisher is None:publisher = ''else:publisher = tag_publisher.next_sibling.next_sibling.text.strip()tag_producer = tag_info.find(name='span', attrs={'class': 'pl'}, string='出品方:')if tag_producer is None:producer = ''else:producer = tag_producer.next_sibling.next_sibling.text.strip()tag_original_title = tag_info.find(name='span', attrs={'class': 'pl'}, string='原作名:')if tag_original_title is None:original_title = ''else:original_title = tag_original_title.next_sibling.strip()tag_translator = tag_info.find(name='span', attrs={'class': 'pl'}, string=' 译者')if tag_translator is None:translator = ''else:translator = tag_translator.next_sibling.next_sibling.text.strip()tag_publication_year = tag_info.find(name='span', attrs={'class': 'pl'}, string='出版年:')if tag_publication_year is None:publication_year = ''else:publication_year = tag_publication_year.next_sibling.strip()tag_page_count = tag_info.find(name='span', attrs={'class': 'pl'}, string='页数:')if tag_page_count is None:page_count = ''else:page_count = tag_page_count.next_sibling.strip()tag_price = tag_info.find(name='span', attrs={'class': 'pl'}, string='定价:')if tag_price is None:price = ''else:price = tag_price.next_sibling.strip()tag_binding = tag_info.find(name='span', attrs={'class': 'pl'}, string='装帧:')if tag_binding is None:binding = ''else:binding = tag_binding.next_sibling.strip()tag_series = tag_info.find(name='span', attrs={'class': 'pl'}, string='丛书:')if tag_series is None:series = ''else:series = tag_series.next_sibling.next_sibling.text.strip()tag_isbn = tag_info.find(name='span', attrs={'class': 'pl'}, string='ISBN:')if tag_isbn is None:isbn = ''else:isbn = tag_isbn.next_sibling.strip()# 评分信息tag_rating_wrap_clearbox = tag_subjectwrap.select_one('#interest_sectl > div')# 评分tag_rating = (tag_rating_wrap_clearbox.select_one('#interest_sectl > div > div.rating_self.clearfix > strong'))if tag_rating is None:rating = ''else:rating = tag_rating.string.strip()# 评论人数tag_rating_sum = tag_rating_wrap_clearbox.select_one('#interest_sectl > div > div.rating_self.clearfix > div > div.rating_sum > span > a > span')if tag_rating_sum is None:rating_sum = ''else:rating_sum = tag_rating_sum.string.strip()# 评论链接comment_link = f'https://book.douban.com/subject/{book_id}/comments/'# 五星比例tag_stars5_starstop = tag_rating_wrap_clearbox.select_one('#interest_sectl > div > span.stars5.starstop')if tag_stars5_starstop is None:stars5_starstop = ''else:stars5_starstop = tag_stars5_starstop.next_sibling.next_sibling.next_sibling.next_sibling.text.strip()# 四星比例tag_stars4_starstop = tag_rating_wrap_clearbox.select_one('#interest_sectl > div > span.stars4.starstop')if tag_stars4_starstop is None:stars4_starstop = ''else:stars4_starstop = tag_stars4_starstop.next_sibling.next_sibling.next_sibling.next_sibling.text.strip()# 三星比例tag_stars3_starstop = tag_rating_wrap_clearbox.select_one('#interest_sectl > div > span.stars3.starstop')if tag_stars3_starstop is None:stars3_starstop = ''else:stars3_starstop = tag_stars3_starstop.next_sibling.next_sibling.next_sibling.next_sibling.text.strip()# 二星比例tag_stars2_starstop = tag_rating_wrap_clearbox.select_one('#interest_sectl > div > span.stars2.starstop')if tag_stars2_starstop is None:stars2_starstop = ''else:stars2_starstop = tag_stars2_starstop.next_sibling.next_sibling.next_sibling.next_sibling.text.strip()# 一星比例tag_stars1_starstop = tag_rating_wrap_clearbox.select_one('#interest_sectl > div > span.stars1.starstop')if tag_stars1_starstop is None:stars1_starstop = ''else:stars1_starstop = tag_stars1_starstop.next_sibling.next_sibling.next_sibling.next_sibling.text.strip()data_dict = {'book_id': book_id,'title': title,'img_src': img_src,'author': author,'publisher': publisher,'producer': producer,'original_title': original_title,'translator': translator,'publication_year': publication_year,'page_count': page_count,'price': price,'binding': binding,'series': series,'isbn': isbn,'rating': rating,'rating_sum': rating_sum,'comment_link': comment_link,'stars5_starstop': stars5_starstop,'stars4_starstop': stars4_starstop,'stars3_starstop': stars3_starstop,'stars2_starstop': stars2_starstop,'stars1_starstop': stars1_starstop}print(f'===========================文件路径:{detail_file},解析后的数据如下:===========================')print(data_dict)print('===========================================================')# 把数据保存到列表中book_data.append(data_dict)count = count + 1if count == 100:df = pd.DataFrame(book_data)if not csv_file_path.exists():df.to_csv(csv_file_dir + csv_file_name, index=False, encoding='utf-8-sig')else:df.to_csv(csv_file_dir + csv_file_name, index=False, encoding='utf-8-sig', mode='a', header=False)book_data = []count = 0if __name__ == '__main__':parse_detail_html_to_csv()

执行过程中打印的部分信息如下图所示:

在这里插入图片描述

csv文件位置及内容如下图所示:

在这里插入图片描述
在这里插入图片描述

2. 数据清洗与存储

2.1 数据清洗

使用pandas进行数据清洗。
空值:除下列说明外,对于空值统一使用未知来填充。
日期:空值使用1970-01-01来填充,缺失月或日用01填充。
页数:空值使用0来填充。
定价:空值使用0来填充。
评分:空值使用0来填充。
评分人数:空值使用0来填充。
星级评价:空值使用0来填充。

2.2 数据存储

把清洗后的数据保存到MySQL中。

2.2.1 表设计

根据图片中的字段,以下是设计的MySQL表结构。我将使用标准的SQL语法来定义这个表,并以表格形式展示。

字段名称数据类型说明
book_idINT书籍的唯一标识符。
titleVARCHAR(255)书名。
img_srcVARCHAR(255)封面图片的网络地址。
authorVARCHAR(255)作者姓名。
publisherVARCHAR(255)出版社名称。
producerVARCHAR(255)制作人或出品方(如果有的话)。
original_titleVARCHAR(255)原版书名(如果是翻译作品,则为原语言书名)。
translatorVARCHAR(255)翻译者姓名(如果有)。
publication_yearDATE出版年份。
page_countINT页数。
priceDECIMAL(10, 2)定价。
bindingVARCHAR(255)装帧类型(如平装、精装等)。
seriesVARCHAR(255)丛书系列名称(如果有的话)。
isbnVARCHAR(20)国际标准书号。
ratingDECIMAL(3, 1)平均评分。
rating_sumINT参与评分的人数。
comment_linkVARCHAR(255)用户评论链接。
stars5_starstopDECIMAL(5, 2)五星评价所占的比例。
stars4_starstopDECIMAL(5, 2)四星评价所占的比例。
stars3_starstopDECIMAL(5, 2)三星评价所占的比例。
stars2_starstopDECIMAL(5, 2)二星评价所占的比例。
stars1_starstopDECIMAL(5, 2)一星评价所占的比例。
2.2.2 表实现

创建数据库douban

create database douban;

切换到数据库douban

use douban;

创建数据表cleaned_douban_books,用于存储清洗后的数据。

CREATE TABLE cleaned_douban_books (book_id INT PRIMARY KEY,title VARCHAR(255),img_src VARCHAR(255),author VARCHAR(255),publisher VARCHAR(255),producer VARCHAR(255),original_title VARCHAR(255),translator VARCHAR(255),publication_year DATE,page_count INT,price DECIMAL(10, 2),binding VARCHAR(255),series VARCHAR(255),isbn VARCHAR(20),rating DECIMAL(3, 1),rating_sum INT,comment_link VARCHAR(255),stars5_starstop DECIMAL(5, 2),stars4_starstop DECIMAL(5, 2),stars3_starstop DECIMAL(5, 2),stars2_starstop DECIMAL(5, 2),stars1_starstop DECIMAL(5, 2)
);

2.3 代码实现

import reimport pandas as pd
from sqlalchemy import create_enginedef read_csv_to_df(file_path):# 加载CSV文件到DataFramedf = pd.read_csv(file_path, encoding='utf-8')return dfdef unify_date_format(date_str):# 检查是否为 NaN 或 Noneif pd.isna(date_str) or date_str is None:return None# 定义一个函数来处理特殊格式的日期def preprocess_date(date_str):# 如果是字符串并且包含中文格式的日期,则进行替换if isinstance(date_str, str) and '年' in date_str and '月' in date_str:return date_str.replace('年', '-').replace('月', '-').replace('日', '')return date_str# 预处理日期字符串processed_date = preprocess_date(date_str)try:# 使用pd.to_datetime尝试转换日期格式date_obj = pd.to_datetime(processed_date, errors='coerce')# 如果只有年份,则添加默认的月份和日子为01if isinstance(date_obj, pd.Timestamp) and len(str(processed_date).split('-')) == 1:date_obj = date_obj.replace(month=1, day=1)# 返回标准化的日期字符串return date_obj.strftime('%Y-%m-%d') if not pd.isna(date_obj) else Noneexcept Exception as e:print(f"Error parsing date '{date_str}': {e}")return '1970-01-01'def clean_price(price_str):if pd.isna(price_str) or not isinstance(price_str, str):return 0# 移除所有非数字字符,保留数字和小数点cleaned = re.sub(r'[^\d./]+', '', price_str)# 处理包含多个价格的情况,这里选择平均值作为代表prices = []for part in cleaned.split('/'):# 进一步清理每个部分,移除非数字和非小数点字符sub_parts = re.findall(r'\d+\.\d+|\d+', part)if sub_parts:try:# 取每个部分的第一个匹配的价格price = float(sub_parts[0])prices.append(price)except ValueError:continueif not prices:return 0# 根据需要选择不同的策略,这里选择平均值avg_price = sum(prices) / len(prices)# 确保保留两位小数return round(avg_price, 2)def clean_percentage(percentage_str):if pd.isna(percentage_str) or not isinstance(percentage_str, str):return 0# 移除百分比符号并转换为浮点数cleaned = re.sub(r'[^\d.]+', '', percentage_str)return round(float(cleaned), 2)def clean_page_count(page_str):if not isinstance(page_str, str) or not page_str.strip():return 0# 移除非数字字符,保留数字和分号cleaned = re.sub(r'[^\d;;]+', '', page_str)# 分离多个页数pages = [int(p) for p in cleaned.split(';') if p]if not pages:return 0# 根据需要选择不同的策略,这里选择最大值max_page = max(pages)return max_page# 定义函数:清理和转换数据格式
def clean_and_transform(df):# 删除book_id相同的数据df.drop_duplicates(subset=['book_id'])df['author'].fillna('未知', inplace=True)df['publisher'].fillna('未知', inplace=True)df['producer'].fillna('未知', inplace=True)df['original_title'].fillna('未知', inplace=True)df['translator'].fillna('未知', inplace=True)# 日期:空值使用1970-01-01来填充,缺失月或日用01填充df['publication_year'] = df['publication_year'].apply(unify_date_format)df['page_count'].fillna(0, inplace=True)df['page_count'] = df['page_count'].apply(clean_page_count)df['page_count'] = df['page_count'].astype(int)df['price'] = df['price'].apply(clean_price)df['binding'].fillna('未知', inplace=True)df['series'].fillna('未知', inplace=True)df['isbn'].fillna('未知', inplace=True)df['rating'].fillna(0, inplace=True)df['rating_sum'].fillna(0, inplace=True)df['rating_sum'] = df['rating_sum'].astype(int)df['stars5_starstop'] = df['stars5_starstop'].apply(lambda x: clean_percentage(x))df['stars4_starstop'] = df['stars4_starstop'].apply(lambda x: clean_percentage(x))df['stars3_starstop'] = df['stars3_starstop'].apply(lambda x: clean_percentage(x))df['stars2_starstop'] = df['stars2_starstop'].apply(lambda x: clean_percentage(x))df['stars1_starstop'] = df['stars1_starstop'].apply(lambda x: clean_percentage(x))return dfdef save_df_to_db(df):# 设置数据库连接信息db_user = 'root'db_password = 'zxcvbq'db_host = '127.0.0.1'  # 或者你的数据库主机地址db_port = '3306'  # MySQL默认端口是3306db_name = 'douban'# 创建数据库引擎engine = create_engine(f'mysql+mysqlconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}')# 将df写入MySQL表df.to_sql(name='cleaned_douban_books', con=engine, if_exists='append', index=False)print("所有csv文件的数据已成功清洗并写入MySQL数据库")if __name__ == '__main__':csv_file = r'..\douban\douban_book\data_csv\douban_books.csv'df = read_csv_to_df(csv_file)df = clean_and_transform(df)save_df_to_db(df)

查看cleaned_douban_books表中的图书数据:

select * from cleaned_douban_books limit 10;

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MIPI_DPU 综合(DPU+MIPI+Demosaic+VDMA 通路)

目录 1. 简介 2. 创建 Platform 2.1 Block Design 2.1.1 DPU PFM Lite 2.1.2 DPU prj 2.1.3 DPU MIPI Platform 2.2 pin 约束 2.2.1 GPIO 约束 2.2.2 IIC 约束 2.1.3 DPHY 约束 3. 报错总结 3.1 AXI_M 必须顺序引用 3.2 DPU 地址分配错误 4. Design Example 4.…

Spring系列一:spring的安装与使用

文章目录 ?? 官方资料 ??Spring5下载??文档介绍 ??Spring5 ??内容介绍??重要概念 ??快速入门 ??Spring操作演示??类加载路径??Spring容器结构剖析??Debug配置 ??实现简单基于XML配置程序 ??Spring原生容器结构梳理??作业布置??Spring课堂练习 …

AutoSar架构学习笔记

1.AUTOSAR&#xff08;Automotive Open System Architecture&#xff0c;汽车开放系统架构&#xff09;是一个针对汽车行业的软件架构标准&#xff0c;旨在提升汽车电子系统的模块化、可扩展性、可重用性和互操作性。AUTOSAR的目标是为汽车电子控制单元&#xff08;ECU&#xf…

Kernel Stack栈溢出攻击及保护绕过

前言 本文介绍Linux内核的栈溢出攻击&#xff0c;和内核一些保护的绕过手法&#xff0c;通过一道内核题及其变体从浅入深一步步走进kernel世界。 QWB_2018_core 题目分析 start.sh qemu-system-x86_64 \-m 128M \-kernel ./bzImage \-initrd ./core.cpio \-append "…

【顶刊TPAMI 2025】多头编码(MHE)之Part 6:极限分类无需预处理

目录 1 标签分解方法的消融研究2 标签分解对泛化的影响3 讨论4 结论 论文&#xff1a;Multi-Head Encoding for Extreme Label Classification 作者&#xff1a;Daojun Liang, Haixia Zhang, Dongfeng Yuan and Minggao Zhang 单位&#xff1a;山东大学 代码&#xff1a;https:…

友元和运算符重载

1. 友元 可以把某些选定的函数看作类的“荣誉函数”&#xff0c;允许它们访问类对象中非公共的成员&#xff0c;就好像它们是类的成员一样&#xff0c;这种函数称为类的友元。友元可以访问类对象的任意成员。 1.1 友元函数 友元函数是一种定义在类外部的普通函数&#xff0…

Git 树形图表不显示问题

注册表修改 ## 注册表 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers 生效 右键重启 windows资源管理器

【MySQL基础篇】三、表结构的操作

文章目录 Ⅰ. 创建表1、语法2、创建表样例3、创建和其它表一样结构的表 Ⅱ. 查看表结构1、查看数据库中的表2、查看指定表的属性3、获取表的创建语句 Ⅲ. 删除表Ⅳ. 修改表结构1、向表中插入新的字段2、删除表中的字段3、修改表名4、修改字段属性 Ⅰ. 创建表 1、语法 create …

aws(学习笔记第二十二课) 复杂的lambda应用程序(python zip打包)

aws(学习笔记第二十二课) 开发复杂的lambda应用程序(python的zip包) 学习内容&#xff1a; 练习使用CloudShell开发复杂lambda应用程序(python) 1. 练习使用CloudShell CloudShell使用背景 复杂的python的lambda程序会有许多依赖的包&#xff0c;如果不提前准备好这些python的…

SAP SD销售模块常见BAPI函数

【SAP系统研究】 #SAP #SD #销售管理 1、销售订单 BAPI_SALESORDER_CREATEFROMDAT2 创建销售订单 BAPI_CUSTOMERRETURN_CREATE 创建退货订单 SD_SALESDOCUMENT_CREATE 创建贷项订单 BAPI_SALESORDER_CHANGE 修改销售订单 STATUS_READ 查看销售订单状态VB销售订单000000 I_CHA…

CSS学习记录21

CSS 工具提示 通过CSS 创建工具提示&#xff08;Tooltip)。 当用户将鼠标指针移动到元素上时&#xff0c;工具提示通常用于提供关于某内容的额外信息&#xff1a; <style> /* Tooltip 容器 */ .tooltip {position: relative;display: inline-block;border-bottom: 1px …

DuckDB:密钥管理器及其应用

密钥管理器(Secrets Manager)为所有使用密钥的后端提供了统一的用户界面。密钥信息可以被限定范围&#xff0c;因此不同的存储前缀可以有不同的密钥信息&#xff0c;例如允许在单个查询中连接跨组织的数据。密钥也可以持久化&#xff0c;这样就不需要在每次启动DuckDB时都指定它…

[cg] android studio 无法调试cpp问题

折腾了好久&#xff0c;native cpp库无法调试问题&#xff0c;原因 下面的Deploy 需要选Apk from app bundle!! 另外就是指定Debug type为Dual&#xff0c;并在Symbol Directories 指定native cpp的so路径 UE项目调试&#xff1a; 使用Android Studio调试虚幻引擎Android项目…

uni-app深度解码:跨平台APP开发的核心引擎与创新实践

在当今数字化浪潮中&#xff0c;移动应用市场呈现出爆炸式增长。为了满足不同用户群体在不同操作系统上的需求&#xff0c;跨平台 APP 开发成为众多开发者的首选策略。uni-app 作为一款领先的跨平台开发框架&#xff0c;以其独特的优势和创新的实践在众多同类产品中脱颖而出。它…

I2C(一):存储器模式:stm32作为主机对AT24C02写读数据

存储器模式&#xff1a;在HAL库中&#xff0c;I2C有专门对存储器外设设置的库函数 I2C&#xff08;一&#xff09;&#xff1a;存储器模式的使用 1、I2C轮询式写读AT24C02一页数据2、I2C轮询式写读AT24C02多页数据3、I2C中断式写读AT24C02一页数据4、I2C使用DMA式写读AT24C02一…

Elasticsearch:减少 Elastic 容器镜像中的 CVE(常见的漏洞和暴露)

作者&#xff1a;来自 Elastic Maxime Greau 在这篇博文中&#xff0c;我们将讨论如何通过在 Elastic 产品中切换到最小基础镜像并优化可扩展漏洞管理程序的工作流程来显著减少 Elastic 容器镜像中的常见漏洞和暴露 (Common Vulnerabilities and Exposures - CVEs)。 基于 Chai…

【AI学习】Transformer深入学习(二):从MHA、MQA、GQA到MLA

前面文章&#xff1a; 《Transformer深入学习&#xff08;一&#xff09;&#xff1a;Sinusoidal位置编码的精妙》 一、MHA、MQA、GQA 为了降低KV cache&#xff0c;MQA、GQA作为MHA的变体&#xff0c;很容易理解。 多头注意力&#xff08;MHA&#xff09;&#xff1a; 多头注…

使用python调用翻译大模型实现本地翻译【exe客户端版】

以前分享过一个 关于python 部署 网页端的 翻译大模型的 文章 有兴趣的小伙伴可以去看一下 https://blog.csdn.net/Drug_/article/details/144488795 今天就再分享一个 使用python 来制作一个 exe 客户端版的 本地大模型。 实际也很简单 只不过把 用 fastApi 框架 做的 网页端…

python3GUI--智慧交通监控与管理系统 By:PyQt5

文章目录 一&#xff0e;前言二&#xff0e;预览三&#xff0e;软件组成&技术难点1.软件组成结构2.技术难点3.项目结构 四&#xff0e;总结 大小&#xff1a;35.5 M&#xff0c;软件安装包放在了这里! 一&#xff0e;前言 博主高产&#xff0c;本次给大家带来一款我自己使…

JavaVue-Get请求 数组参数(qs格式化前端数据)

前言 现在管理系统&#xff0c;像若依&#xff0c;表格查询一般会用Get请求&#xff0c;把页面的查询条件传递给后台。其中大部分页面会有日期时间范围查询这时候&#xff0c;为了解决请求参数中的数组文件&#xff0c;前台就会在请求前拦截参数中的日期数组数据&#xff0c;然…