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

打印 上一主题 下一主题

主题 1516|帖子 1516|积分 4548


前言

在数字化时代,网络爬虫技能为我们提供了强盛的数据获取本领,使得从各类网站提取信息变得更加高效和便捷。豆瓣读书作为一个广受接待的图书评价和保举平台,汇聚了大量的册本信息,包括书名、作者、出书社、评分等。这些信息不仅对读者选择图书有帮助,也为出书商和研究职员提供了宝贵的数据资源。
本项目旨在通过 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文件中。
  1. <div class="subjectwrap clearfix">
  2. <div class="subject clearfix">
  3. <div id="mainpic" class="">
  4.   <a class="nbg" href="https://img1.doubanio.com/view/subject/l/public/s34971089.jpg" title="再造乡土">
  5.       <img src="https://img1.doubanio.com/view/subject/s/public/s34971089.jpg" title="点击看大图" alt="再造乡土" rel="v:photo" style="max-width: 135px;max-height: 200px;">
  6.   </a>
  7. </div>
  8. <div id="info" class="">
  9.     <span>
  10.       <span class="pl"> 作者</span>:
  11.             <a class="" href="/author/4639586">(美)萨拉·法默</a>
  12.     </span><br>
  13.     <span class="pl">出版社:</span>
  14.       <a href="https://book.douban.com/press/2476">广西师范大学出版社</a>
  15.     <br>
  16.     <span class="pl">出品方:</span>
  17.       <a href="https://book.douban.com/producers/795">望mountain</a>
  18.     <br>
  19.     <span class="pl">副标题:</span> 1945年后法国农村社会的衰落与重生<br>
  20.     <span class="pl">原作名:</span> Rural Inventions: The French Countryside after 1945<br>
  21.     <span>
  22.       <span class="pl"> 译者</span>:
  23.             <a class="" href="/search/%E5%8F%B6%E8%97%8F">叶藏</a>
  24.     </span><br>
  25.     <span class="pl">出版年:</span> 2024-11<br>
  26.     <span class="pl">页数:</span> 288<br>
  27.     <span class="pl">定价:</span> 79.20元<br>
  28.     <span class="pl">装帧:</span> 精装<br>
  29.       <span class="pl">ISBN:</span> 9787559874597<br>
  30. </div>
  31. </div>
  32. <div id="interest_sectl" class="">
  33.   <div class="rating_wrap clearbox" rel="v:rating">
  34.     <div class="rating_logo">
  35.             豆瓣评分
  36.     </div>
  37.     <div class="rating_self clearfix" typeof="v:Rating">
  38.       <strong class="ll rating_num " property="v:average"> 8.5 </strong>
  39.       <span property="v:best" content="10.0"></span>
  40.       <div class="rating_right ">
  41.           <div class="ll bigstar45"></div>
  42.             <div class="rating_sum">
  43.                 <span class="">
  44.                     <a href="comments" class="rating_people"><span property="v:votes">55</span>人评价</a>
  45.                 </span>
  46.             </div>
  47.       </div>
  48.     </div>
  49. <span class="stars5 starstop" title="力荐">
  50.     5星
  51. </span>
  52. <div class="power" style="width:37px"></div>
  53.             <span class="rating_per">29.1%</span>
  54.             <br>
  55. <span class="stars4 starstop" title="推荐">
  56.     4星
  57. </span>
  58. <div class="power" style="width:64px"></div>
  59.             <span class="rating_per">49.1%</span>
  60.             <br>
  61. <span class="stars3 starstop" title="还行">
  62.     3星
  63. </span>
  64. <div class="power" style="width:26px"></div>
  65.             <span class="rating_per">20.0%</span>
  66.             <br>
  67. <span class="stars2 starstop" title="较差">
  68.     2星
  69. </span>
  70. <div class="power" style="width:2px"></div>
  71.             <span class="rating_per">1.8%</span>
  72.             <br>
  73. <span class="stars1 starstop" title="很差">
  74.     1星
  75. </span>
  76. <div class="power" style="width:0px"></div>
  77.             <span class="rating_per">0.0%</span>
  78.             <br>
  79.   </div>
  80. </div>
  81. </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条数据,而且带有两个参数(start、type;start体现每页开始位置,每页20条数据),由此可以推断每一页的链接为:https://book.douban.com/tag/<标签名称>?start=<20的倍数>&type=T。然后从每一页中剖析出每个图书的链接

1.2.3 爬取单个图书页面

获得每个图书的链接后,就可以根据链接生存每个图书的html文件。然后就可以使用BeautifulSoup从该页面中剖析出图书的信息。
单个图书的页面如下图所示:

1.3 内容所在的标签定位

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

复制后的selector如下:
  1. #wrapper > h1 > span
复制代码
2. 数据用途

2.1 基础分析


  • 形貌性统计

    • 盘算册本代价、页数等数值型字段的平均值、中位数、最大值、最小值以及标准差。
    • 统计不同装帧类型(binding)或出书社(publisher)的册本数量。

  • 频率分布

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

  • 简单关系探索

    • 探索册本代价与评分之间的简单相关性。
    • 研究册本页数与评分的关系,看是否有显着的关联。

  • 分类汇总

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

2.2 高级分析


  • 预测建模

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

  • 聚类分析

    • 对册本进行聚类,找出具有相似特性的册本群体,例如相似的主题、读者群体或市场体现。
    • 根据用户批评链接中的文本信息进行主题建模,以辨认常见的读者关注点或反馈类型。

  • 因果分析

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

  • 时间序列分析

    • 如果有一连多年的数据,可以对出书年份和销量等进行时间序列分析,预测将来的趋势。
    • 研究特定变乱(如作者获得奖项)对册本销量的时间影响。

  • 网络分析

    • 构建作者相助网络或册本引用网络,探索学术或文学领域内的相助模式和影响力传播。

  • 感情分析

    • 对用户批评链接指向的内容进行感情分析,理解读者对册本的感情倾向。

  • 多变量回归分析

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

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

页面如下图所示:

代码实现:
  1. import random
  2. import time
  3. from pathlib import Path
  4. import requests
  5. def get_request(url, **kwargs):
  6.     time.sleep(random.uniform(0.1, 2))
  7.     print(f'===============================地址:{url} ===============================')
  8.     # 定义一组User-Agent字符串
  9.     user_agents = [
  10.         # Chrome
  11.         'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
  12.         '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',
  13.         'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
  14.         # Firefox
  15.         'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0',
  16.         'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0',
  17.         'Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/117.0',
  18.         # Edge
  19.         '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',
  20.         # Safari
  21.         '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',
  22.     ]
  23.     # 请求头
  24.     headers = {
  25.         'User-Agent': random.choice(user_agents)
  26.     }
  27.     # 用户名密码认证(私密代理/独享代理)
  28.     username = ""
  29.     password = ""
  30.     proxies = {
  31.         "http": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,
  32.                                                         "proxy": '36.25.243.5:11768'},
  33.         "https": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,
  34.                                                          "proxy": '36.25.243.5:11768'}
  35.     }
  36.     max_retries = 3
  37.     for attempt in range(max_retries):
  38.         try:
  39.             response = requests.get(url=url, timeout=10, headers=headers, **kwargs)
  40.             # response = requests.get(url=url, timeout=10, headers=headers, proxies=proxies, **kwargs)
  41.             if response.status_code == 200:
  42.                 return response
  43.             else:
  44.                 print(f"请求失败,状态码: {response.status_code},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")
  45.         except requests.exceptions.RequestException as e:
  46.             print(f"请求过程中发生异常: {e},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")
  47.         # 如果不是最后一次尝试,则等待一段时间再重试
  48.         if attempt < max_retries - 1:
  49.             time.sleep(random.uniform(1, 2))
  50.     print('================多次请求失败,请查看异常情况================')
  51.     return None  # 或者返回最后一次的响应,取决于你的需求
  52. def save_book_html_file(save_dir, file_name, content):
  53.     dir_path = Path(save_dir)
  54.     # 确保保存目录存在,如果不存在则创建所有必要的父级目录
  55.     dir_path.mkdir(parents=True, exist_ok=True)
  56.     # 使用 'with' 语句打开文件以确保正确关闭文件流
  57.     with open(save_dir + file_name, 'w', encoding='utf-8') as fp:
  58.         print(f"==============================={save_dir + file_name} 文件已保存===============================")
  59.         fp.write(str(content))
  60. def download_book_tag():
  61.     save_dir = '../douban/douban_book/douban_book_tag/'
  62.     file_name = 'douban_book_all_tag.html'
  63.     book_tag_url = 'https://book.douban.com/tag/?view=type&icn=index-sorttags-all'
  64.     tag_file_path = Path(save_dir + file_name)
  65.     if tag_file_path.exists() and tag_file_path.is_file():
  66.         print(f'\n===============================文件 {tag_file_path} 已存在===============================')
  67.     else:
  68.         print(f'===============================文件 {tag_file_path} 不存在,正在下载...===============================')
  69.         save_book_html_file(save_dir=save_dir, file_name=file_name, content=get_request(book_tag_url).text)
  70. if __name__ == '__main__':
  71.     download_book_tag()
复制代码
运行结果如下图所示:

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

生存后的文件如下图:

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

基于上面的爬取标签分类继承实现的代码,使用BeautifulSoup剖析标签分类html后,根据获取的标签分类名称和链接循环获取每个分类下的所有html页面。
  1. import random
  2. import time
  3. from pathlib import Path
  4. import requests
  5. from bs4 import BeautifulSoup
  6. # 快代理试用:https://www.kuaidaili.com/freetest/
  7. def get_request(url, **kwargs):
  8.     time.sleep(random.uniform(0.1, 2))
  9.     print(f'===============================地址:{url} ===============================')
  10.     # 定义一组User-Agent字符串
  11.     user_agents = [
  12.         # Chrome
  13.         'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
  14.         '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',
  15.         'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
  16.         # Firefox
  17.         'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0',
  18.         'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0',
  19.         'Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/117.0',
  20.         # Edge
  21.         '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',
  22.         # Safari
  23.         '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',
  24.     ]
  25.     # 请求头
  26.     headers = {
  27.         'User-Agent': random.choice(user_agents)
  28.     }
  29.     # 用户名密码认证(私密代理/独享代理)
  30.     username = "17687015657"
  31.     password = "qvbgms8w"
  32.     proxies = {
  33.         "http": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,
  34.                                                         "proxy": '36.25.243.5:11768'},
  35.         "https": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,
  36.                                                          "proxy": '36.25.243.5:11768'}
  37.     }
  38.     max_retries = 3
  39.     for attempt in range(max_retries):
  40.         try:
  41.             response = requests.get(url=url, timeout=10, headers=headers, **kwargs)
  42.             # response = requests.get(url=url, timeout=10, headers=headers, proxies=proxies, **kwargs)
  43.             if response.status_code == 200:
  44.                 return response
  45.             else:
  46.                 print(f"请求失败,状态码: {response.status_code},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")
  47.         except requests.exceptions.RequestException as e:
  48.             print(f"请求过程中发生异常: {e},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")
  49.         # 如果不是最后一次尝试,则等待一段时间再重试
  50.         if attempt < max_retries - 1:
  51.             time.sleep(random.uniform(1, 2))
  52.     print('================多次请求失败,请查看异常情况================')
  53.     return None  # 或者返回最后一次的响应,取决于你的需求
  54. def save_book_html_file(save_dir, file_name, content):
  55.     dir_path = Path(save_dir)
  56.     # 确保保存目录存在,如果不存在则创建所有必要的父级目录
  57.     dir_path.mkdir(parents=True, exist_ok=True)
  58.     # 使用 'with' 语句打开文件以确保正确关闭文件流
  59.     with open(save_dir + file_name, 'w', encoding='utf-8') as fp:
  60.         print(f"==============================={save_dir + file_name} 文件已保存===============================")
  61.         fp.write(str(content))
  62. def download_book_tag():
  63.     save_dir = '../douban/douban_book/douban_book_tag/'
  64.     file_name = 'douban_book_all_tag.html'
  65.     book_tag_url = 'https://book.douban.com/tag/?view=type&icn=index-sorttags-all'
  66.     tag_file_path = Path(save_dir + file_name)
  67.     if tag_file_path.exists() and tag_file_path.is_file():
  68.         print(f'\n===============================文件 {tag_file_path} 已存在===============================')
  69.     else:
  70.         print(f'===============================文件 {tag_file_path} 不存在,正在下载...===============================')
  71.         save_book_html_file(save_dir=save_dir, file_name=file_name, content=get_request(book_tag_url).text)
  72. def get_soup(markup):
  73.     return BeautifulSoup(markup=markup, features='lxml')
  74. def get_book_type_and_href():
  75.     # 定义HTML文件路径
  76.     file = '../douban/douban_book/douban_book_tag/douban_book_all_tag.html'
  77.     # 初始化一个空字典用于存储标签名称和对应的链接
  78.     name_href_result = {}
  79.     # 定义豆瓣书籍的基础URL,用于拼接完整的链接
  80.     book_base_url = 'https://book.douban.com'
  81.     # 打开并读取HTML文件内容
  82.     with open(file=file, mode='r', encoding='utf-8') as fp:
  83.         # 使用BeautifulSoup解析HTML内容
  84.         soup = get_soup(fp)
  85.         # 选择包含所有标签链接的主要容器
  86.         tag = soup.select_one('#content > div > div.article > div:nth-child(2)')
  87.         # 选择所有包含标签链接的表格行(每个类别下的标签表)
  88.         tables = tag.select('div > a.tag-title-wrapper + table.tagCol')
  89.         # 遍历每个表格
  90.         for table in tables:
  91.             # 选择表格中的所有行(tr标签)
  92.             tr_tags = table.select('tr')
  93.             # 遍历每一行
  94.             for tr_tag in tr_tags:
  95.                 # 选择行中的所有单元格(td标签)
  96.                 td_tags = tr_tag.select('td')
  97.                 # 遍历每个单元格
  98.                 for td_tag in td_tags:
  99.                     # 选择单元格中的第一个a标签(如果存在)
  100.                     a_tag = td_tag.select_one('a')
  101.                     # 如果找到了a标签,则提取文本和href属性
  102.                     if a_tag:
  103.                         # 提取a标签的文本内容,并去除两端空白字符
  104.                         tag_text = a_tag.string
  105.                         # 获取a标签的href属性,并与基础URL拼接成完整链接
  106.                         tag_href = book_base_url + a_tag.attrs.get('href')
  107.                         # 将提取到的标签文本和链接添加到结果字典中
  108.                         name_href_result[tag_text] = tag_href
  109.     # 返回包含所有标签名称和对应链接的字典
  110.     return name_href_result
  111. def get_book_data_dagai(name, start):
  112.     book_tag_base_url = 'https://book.douban.com/tag/' + name
  113.     payload = {
  114.         'start': start,
  115.         'type': 'T'
  116.     }
  117.     response = get_request(book_tag_base_url, params=payload)
  118.     if response is None:
  119.         return None
  120.     return response.text
  121. def download_book_data_dagai(name, start):
  122.     save_dir = '../douban/douban_book/douban_book_data_dagai/'
  123.     file_name = f'douban_book_data_dagai_{name}_{start}.html'
  124.     dagai_file_path = Path(save_dir + file_name)
  125.     if dagai_file_path.exists() and dagai_file_path.is_file():
  126.         print(f'===============================文件 {dagai_file_path} 已存在===============================')
  127.     else:
  128.         print(
  129.             f'===============================文件 {dagai_file_path} 不存在,正在下载...===============================')
  130.         content = get_book_data_dagai(name, start)
  131.         if content is None:
  132.             return None
  133.         # 判断是否是最后一页
  134.         soup = get_soup(content)
  135.         p_tag = soup.select_one('#subject_list > p')
  136.         if p_tag is not None:
  137.             print(f"===============================分类 {name} 的网页爬取完成===============================")
  138.             return True
  139.         save_book_html_file(save_dir=save_dir, file_name=file_name, content=content)
  140. if __name__ == '__main__':
  141.     download_book_tag()
  142.     book_type = get_book_type_and_href()
  143.     book_type_name = book_type.keys()
  144.     print(book_type_name)
  145.     for type_name in book_type_name:
  146.         print(f'===============================图书分类标签:{type_name}===============================')
  147.         start_ = 0
  148.         while True:
  149.             flag = download_book_data_dagai(type_name, start_)
  150.             start_ = start_ + 20
  151.             if flag is None:
  152.                 continue
  153.             if flag:
  154.                 print(f'======================================图书分类标签 {type_name} 的大概html下载完成======================================')
  155.                 break
复制代码
实验过程中打印的部分信息如下图所示:

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

3. 爬取单个图书的html

基于上面的爬取单个分类的所有页面继承实现的代码,使用BeautifulSoup剖析每一页的html后,根据获取的单个图书链接获取html页面。
  1. import random
  2. import time
  3. from pathlib import Path
  4. import requests
  5. from bs4 import BeautifulSoup
  6. # 快代理试用:https://www.kuaidaili.com/freetest/
  7. def get_request(url, **kwargs):
  8.     time.sleep(random.uniform(0.1, 2))
  9.     print(f'===============================地址:{url} ===============================')
  10.     # 定义一组User-Agent字符串
  11.     user_agents = [
  12.         # Chrome
  13.         'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
  14.         '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',
  15.         'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
  16.         # Firefox
  17.         'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0',
  18.         'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0',
  19.         'Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/117.0',
  20.         # Edge
  21.         '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',
  22.         # Safari
  23.         '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',
  24.     ]
  25.     # 请求头
  26.     headers = {
  27.         'User-Agent': random.choice(user_agents)
  28.     }
  29.     # 用户名密码认证(私密代理/独享代理)
  30.     username = ""
  31.     password = ""
  32.     proxies = {
  33.         "http": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,
  34.                                                         "proxy": '36.25.243.5:11768'},
  35.         "https": "http://%(user)s:%(pwd)s@%(proxy)s/" % {"user": username, "pwd": password,
  36.                                                          "proxy": '36.25.243.5:11768'}
  37.     }
  38.     max_retries = 3
  39.     for attempt in range(max_retries):
  40.         try:
  41.             response = requests.get(url=url, timeout=10, headers=headers, **kwargs)
  42.             # response = requests.get(url=url, timeout=10, headers=headers, proxies=proxies, **kwargs)
  43.             if response.status_code == 200:
  44.                 return response
  45.             else:
  46.                 print(f"请求失败,状态码: {response.status_code},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")
  47.         except requests.exceptions.RequestException as e:
  48.             print(f"请求过程中发生异常: {e},正在重新发送请求 (尝试 {attempt + 1}/{max_retries})")
  49.         # 如果不是最后一次尝试,则等待一段时间再重试
  50.         if attempt < max_retries - 1:
  51.             time.sleep(random.uniform(1, 2))
  52.     print('================多次请求失败,请查看异常情况================')
  53.     return None  # 或者返回最后一次的响应,取决于你的需求
  54. def save_book_html_file(save_dir, file_name, content):
  55.     dir_path = Path(save_dir)
  56.     # 确保保存目录存在,如果不存在则创建所有必要的父级目录
  57.     dir_path.mkdir(parents=True, exist_ok=True)
  58.     # 使用 'with' 语句打开文件以确保正确关闭文件流
  59.     with open(save_dir + file_name, 'w', encoding='utf-8') as fp:
  60.         print(f"==============================={save_dir + file_name} 文件已保存===============================")
  61.         fp.write(str(content))
  62. def download_book_tag():
  63.     save_dir = '../douban/douban_book/douban_book_tag/'
  64.     file_name = 'douban_book_all_tag.html'
  65.     book_tag_url = 'https://book.douban.com/tag/?view=type&icn=index-sorttags-all'
  66.     tag_file_path = Path(save_dir + file_name)
  67.     if tag_file_path.exists() and tag_file_path.is_file():
  68.         print(f'\n===============================文件 {tag_file_path} 已存在===============================')
  69.     else:
  70.         print(f'===============================文件 {tag_file_path} 不存在,正在下载...===============================')
  71.         save_book_html_file(save_dir=save_dir, file_name=file_name, content=get_request(book_tag_url).text)
  72. def get_soup(markup):
  73.     return BeautifulSoup(markup=markup, features='lxml')
  74. def get_book_type_and_href():
  75.     # 定义HTML文件路径
  76.     file = '../douban/douban_book/douban_book_tag/douban_book_all_tag.html'
  77.     # 初始化一个空字典用于存储标签名称和对应的链接
  78.     name_href_result = {}
  79.     # 定义豆瓣书籍的基础URL,用于拼接完整的链接
  80.     book_base_url = 'https://book.douban.com'
  81.     # 打开并读取HTML文件内容
  82.     with open(file=file, mode='r', encoding='utf-8') as fp:
  83.         # 使用BeautifulSoup解析HTML内容
  84.         soup = get_soup(fp)
  85.         # 选择包含所有标签链接的主要容器
  86.         tag = soup.select_one('#content > div > div.article > div:nth-child(2)')
  87.         # 选择所有包含标签链接的表格行(每个类别下的标签表)
  88.         tables = tag.select('div > a.tag-title-wrapper + table.tagCol')
  89.         # 遍历每个表格
  90.         for table in tables:
  91.             # 选择表格中的所有行(tr标签)
  92.             tr_tags = table.select('tr')
  93.             # 遍历每一行
  94.             for tr_tag in tr_tags:
  95.                 # 选择行中的所有单元格(td标签)
  96.                 td_tags = tr_tag.select('td')
  97.                 # 遍历每个单元格
  98.                 for td_tag in td_tags:
  99.                     # 选择单元格中的第一个a标签(如果存在)
  100.                     a_tag = td_tag.select_one('a')
  101.                     # 如果找到了a标签,则提取文本和href属性
  102.                     if a_tag:
  103.                         # 提取a标签的文本内容,并去除两端空白字符
  104.                         tag_text = a_tag.string
  105.                         # 获取a标签的href属性,并与基础URL拼接成完整链接
  106.                         tag_href = book_base_url + a_tag.attrs.get('href')
  107.                         # 将提取到的标签文本和链接添加到结果字典中
  108.                         name_href_result[tag_text] = tag_href
  109.     # 返回包含所有标签名称和对应链接的字典
  110.     return name_href_result
  111. def get_book_data_dagai(name, start):
  112.     book_tag_base_url = 'https://book.douban.com/tag/' + name
  113.     payload = {
  114.         'start': start,
  115.         'type': 'T'
  116.     }
  117.     response = get_request(book_tag_base_url, params=payload)
  118.     if response is None:
  119.         return None
  120.     return response.text
  121. def download_book_data_dagai(name, start):
  122.     save_dir = '../douban/douban_book/douban_book_data_dagai/'
  123.     file_name = f'douban_book_data_dagai_{name}_{start}.html'
  124.     dagai_file_path = Path(save_dir + file_name)
  125.     if dagai_file_path.exists() and dagai_file_path.is_file():
  126.         print(f'===============================文件 {dagai_file_path} 已存在===============================')
  127.     else:
  128.         print(
  129.             f'===============================文件 {dagai_file_path} 不存在,正在下载...===============================')
  130.         content = get_book_data_dagai(name, start)
  131.         if content is None:
  132.             return None
  133.         # 判断是否是最后一页
  134.         soup = get_soup(content)
  135.         p_tag = soup.select_one('#subject_list > p')
  136.         if p_tag is not None:
  137.             print(f"===============================分类 {name} 的网页爬取完成===============================")
  138.             return True
  139.         save_book_html_file(save_dir=save_dir, file_name=file_name, content=content)
  140. def download_book_data_detail():
  141.     save_dir = '../douban/douban_book/douban_book_data_detail/'
  142.     dagai_dir = Path('../douban/douban_book/douban_book_data_dagai/')
  143.     dagai_file_list = dagai_dir.rglob('*.html')
  144.     for dagai_file in dagai_file_list:
  145.         soup = get_soup(markup=open(file=dagai_file, mode='r', encoding='utf-8'))
  146.         a_tag_list = soup.select('#subject_list > ul > li  h2 > a')
  147.         for a_tag in a_tag_list:
  148.             href = a_tag.attrs.get('href')
  149.             book_id = href.split('/')[-2]
  150.             file_name = f'douban_book_data_detail_{book_id}.html'
  151.             detail_file_path = Path(save_dir + file_name)
  152.             if detail_file_path.exists() and detail_file_path.is_file():
  153.                 print(f'===============================文件 {detail_file_path} 已存在===============================')
  154.             else:
  155.                 print(
  156.                     f'===============================文件 {detail_file_path} 不存在,正在下载...===============================')
  157.                 response = get_request(href)
  158.                 if response is None:
  159.                     continue
  160.                 save_book_html_file(save_dir, file_name, response.text)
  161. def print_in_rows(items, items_per_row=20):
  162.     for index, name in enumerate(items, start=1):
  163.         print(f'{name}', end=' ')
  164.         if index % items_per_row == 0:
  165.             print()
  166. if __name__ == '__main__':
  167.     download_book_tag()
  168.     book_type = get_book_type_and_href()
  169.     book_type_name = book_type.keys()
  170.     print(book_type_name)
  171.     for type_name in book_type_name:
  172.         print(f'===============================图书分类标签:{type_name}===============================')
  173.         start_ = 0
  174.         while True:
  175.             flag = download_book_data_dagai(type_name, start_)
  176.             start_ = start_ + 20
  177.             if flag is None:
  178.                 continue
  179.             if flag:
  180.                 print(f'======================================图书分类标签 {type_name} 的大概html下载完成======================================')
  181.                 break
  182.     download_book_data_detail()
复制代码
实验过程中打印的部分信息如下图所示:

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


四、数据处理与存储

1. 剖析html并把数据生存到csv文件

使用BeautifulSoup从html文档中剖析出单个图书的信息,循环剖析出多个图书数据后,把数据生存到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文件中。
  1. from pathlib import Pathimport pandas as pdfrom 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 = 0    for 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
  2. ').string        tag_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 + 1        if 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。
  1. create database douban;
复制代码
切换到数据库douban。
  1. use douban;
复制代码
创建数据表cleaned_douban_books,用于存储洗濯后的数据。
  1. CREATE TABLE cleaned_douban_books (
  2.     book_id INT PRIMARY KEY,
  3.     title VARCHAR(255),
  4.     img_src VARCHAR(255),
  5.     author VARCHAR(255),
  6.     publisher VARCHAR(255),
  7.     producer VARCHAR(255),
  8.     original_title VARCHAR(255),
  9.     translator VARCHAR(255),
  10.     publication_year DATE,
  11.     page_count INT,
  12.     price DECIMAL(10, 2),
  13.     binding VARCHAR(255),
  14.     series VARCHAR(255),
  15.     isbn VARCHAR(20),
  16.     rating DECIMAL(3, 1),
  17.     rating_sum INT,
  18.     comment_link VARCHAR(255),
  19.     stars5_starstop DECIMAL(5, 2),
  20.     stars4_starstop DECIMAL(5, 2),
  21.     stars3_starstop DECIMAL(5, 2),
  22.     stars2_starstop DECIMAL(5, 2),
  23.     stars1_starstop DECIMAL(5, 2)
  24. );
复制代码
2.3 代码实现

  1. import re
  2. import pandas as pd
  3. from sqlalchemy import create_engine
  4. def read_csv_to_df(file_path):
  5.     # 加载CSV文件到DataFrame
  6.     df = pd.read_csv(file_path, encoding='utf-8')
  7.     return df
  8. def unify_date_format(date_str):
  9.     # 检查是否为 NaN 或 None
  10.     if pd.isna(date_str) or date_str is None:
  11.         return None
  12.     # 定义一个函数来处理特殊格式的日期
  13.     def preprocess_date(date_str):
  14.         # 如果是字符串并且包含中文格式的日期,则进行替换
  15.         if isinstance(date_str, str) and '年' in date_str and '月' in date_str:
  16.             return date_str.replace('年', '-').replace('月', '-').replace('日', '')
  17.         return date_str
  18.     # 预处理日期字符串
  19.     processed_date = preprocess_date(date_str)
  20.     try:
  21.         # 使用pd.to_datetime尝试转换日期格式
  22.         date_obj = pd.to_datetime(processed_date, errors='coerce')
  23.         # 如果只有年份,则添加默认的月份和日子为01
  24.         if isinstance(date_obj, pd.Timestamp) and len(str(processed_date).split('-')) == 1:
  25.             date_obj = date_obj.replace(month=1, day=1)
  26.         # 返回标准化的日期字符串
  27.         return date_obj.strftime('%Y-%m-%d') if not pd.isna(date_obj) else None
  28.     except Exception as e:
  29.         print(f"Error parsing date '{date_str}': {e}")
  30.         return '1970-01-01'
  31. def clean_price(price_str):
  32.     if pd.isna(price_str) or not isinstance(price_str, str):
  33.         return 0
  34.     # 移除所有非数字字符,保留数字和小数点
  35.     cleaned = re.sub(r'[^\d./]+', '', price_str)
  36.     # 处理包含多个价格的情况,这里选择平均值作为代表
  37.     prices = []
  38.     for part in cleaned.split('/'):
  39.         # 进一步清理每个部分,移除非数字和非小数点字符
  40.         sub_parts = re.findall(r'\d+\.\d+|\d+', part)
  41.         if sub_parts:
  42.             try:
  43.                 # 取每个部分的第一个匹配的价格
  44.                 price = float(sub_parts[0])
  45.                 prices.append(price)
  46.             except ValueError:
  47.                 continue
  48.     if not prices:
  49.         return 0
  50.     # 根据需要选择不同的策略,这里选择平均值
  51.     avg_price = sum(prices) / len(prices)
  52.     # 确保保留两位小数
  53.     return round(avg_price, 2)
  54. def clean_percentage(percentage_str):
  55.     if pd.isna(percentage_str) or not isinstance(percentage_str, str):
  56.         return 0
  57.     # 移除百分比符号并转换为浮点数
  58.     cleaned = re.sub(r'[^\d.]+', '', percentage_str)
  59.     return round(float(cleaned), 2)
  60. def clean_page_count(page_str):
  61.     if not isinstance(page_str, str) or not page_str.strip():
  62.         return 0
  63.     # 移除非数字字符,保留数字和分号
  64.     cleaned = re.sub(r'[^\d;;]+', '', page_str)
  65.     # 分离多个页数
  66.     pages = [int(p) for p in cleaned.split(';') if p]
  67.     if not pages:
  68.         return 0
  69.     # 根据需要选择不同的策略,这里选择最大值
  70.     max_page = max(pages)
  71.     return max_page
  72. # 定义函数:清理和转换数据格式
  73. def clean_and_transform(df):
  74.     # 删除book_id相同的数据
  75.     df.drop_duplicates(subset=['book_id'])
  76.     df['author'].fillna('未知', inplace=True)
  77.     df['publisher'].fillna('未知', inplace=True)
  78.     df['producer'].fillna('未知', inplace=True)
  79.     df['original_title'].fillna('未知', inplace=True)
  80.     df['translator'].fillna('未知', inplace=True)
  81.     # 日期:空值使用1970-01-01来填充,缺失月或日用01填充
  82.     df['publication_year'] = df['publication_year'].apply(unify_date_format)
  83.     df['page_count'].fillna(0, inplace=True)
  84.     df['page_count'] = df['page_count'].apply(clean_page_count)
  85.     df['page_count'] = df['page_count'].astype(int)
  86.     df['price'] = df['price'].apply(clean_price)
  87.     df['binding'].fillna('未知', inplace=True)
  88.     df['series'].fillna('未知', inplace=True)
  89.     df['isbn'].fillna('未知', inplace=True)
  90.     df['rating'].fillna(0, inplace=True)
  91.     df['rating_sum'].fillna(0, inplace=True)
  92.     df['rating_sum'] = df['rating_sum'].astype(int)
  93.     df['stars5_starstop'] = df['stars5_starstop'].apply(lambda x: clean_percentage(x))
  94.     df['stars4_starstop'] = df['stars4_starstop'].apply(lambda x: clean_percentage(x))
  95.     df['stars3_starstop'] = df['stars3_starstop'].apply(lambda x: clean_percentage(x))
  96.     df['stars2_starstop'] = df['stars2_starstop'].apply(lambda x: clean_percentage(x))
  97.     df['stars1_starstop'] = df['stars1_starstop'].apply(lambda x: clean_percentage(x))
  98.     return df
  99. def save_df_to_db(df):
  100.     # 设置数据库连接信息
  101.     db_user = 'root'
  102.     db_password = 'zxcvbq'
  103.     db_host = '127.0.0.1'  # 或者你的数据库主机地址
  104.     db_port = '3306'  # MySQL默认端口是3306
  105.     db_name = 'douban'
  106.     # 创建数据库引擎
  107.     engine = create_engine(f'mysql+mysqlconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}')
  108.     # 将df写入MySQL表
  109.     df.to_sql(name='cleaned_douban_books', con=engine, if_exists='append', index=False)
  110.     print("所有csv文件的数据已成功清洗并写入MySQL数据库")
  111. if __name__ == '__main__':
  112.     csv_file = r'..\douban\douban_book\data_csv\douban_books.csv'
  113.     df = read_csv_to_df(csv_file)
  114.     df = clean_and_transform(df)
  115.     save_df_to_db(df)
复制代码
查看cleaned_douban_books表中的图书数据:
  1. select * from cleaned_douban_books limit 10;
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

八卦阵

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表