1. 项目背景与核心需求最近在整理个人阅读清单时发现Goodreads的Listopia功能提供了大量优质书单资源。但手动记录每本书的书名、作者和评分实在太费时间。作为Python开发者我决定写一个爬虫来自动化这个过程并实现数据清洗和持久化存储。这个爬虫需要解决几个关键问题从Listopia页面准确提取书名和作者信息经常混杂在HTML标签中处理评分数据的各种显示格式星级、文字描述等清洗数据中的异常值和格式不一致问题提供CSV导出和SQLite存储两种持久化方案2. 技术选型与工具准备2.1 核心工具链import requests from bs4 import BeautifulSoup import pandas as pd import sqlite3 import re选择这些库的原因requests比urllib更人性化的HTTP库适合快速开发BeautifulSoupHTML解析的瑞士军刀对标签处理特别友好pandas数据清洗和CSV导出的最佳选择sqlite3Python内置无需额外安装适合小型项目持久化2.2 开发环境配置建议使用虚拟环境避免依赖冲突python -m venv goodreads-scraper source goodreads-scraper/bin/activate # Linux/Mac goodreads-scraper\Scripts\activate # Windows pip install requests beautifulsoup4 pandas3. 页面分析与爬取策略3.1 Goodreads页面结构解析以Best Books of 2022书单为例https://www.goodreads.com/list/show/175269.Best_Books_of_2022关键DOM结构tr itemscope itemtypehttp://schema.org/Book td classnumber1/td td classcover a href/book/show/12345img.../a /td td classmain div classtitle a href/book/show/12345 itempropurl span itempropnameBook Title/span /a by span itempropauthorAuthor Name/span /div div classrating4.12 avg rating/div /td /tr3.2 分页处理逻辑Goodreads使用传统的页码分页base_url https://www.goodreads.com/list/show/175269 for page in range(1, total_pages1): url f{base_url}?page{page} response requests.get(url, headersheaders) # 解析逻辑...注意务必设置合理的请求间隔建议3-5秒避免触发反爬机制4. 核心数据提取实现4.1 书名与作者提取def extract_book_info(book_element): title book_element.find(span, itempropname).text.strip() author book_element.find(span, itempropauthor).text.strip() # 处理作者名中的冗余信息 author re.sub(r,.*$, , author) # 去除,...部分 author re.sub(r\s, , author) # 合并多余空格 return title, author4.2 评分数据解析评分可能以多种形式出现4.12 avg rating → 提取4.12really liked it 4.00 → 提取4.00★★★★☆ → 转换为4.0处理代码def parse_rating(rating_text): # 匹配数字评分 num_match re.search(r(\d\.\d{2}), rating_text) if num_match: return float(num_match.group(1)) # 处理星级评分 star_count rating_text.count(★) half_star 0.5 if ½ in rating_text else 0 return star_count half_star5. 数据清洗与标准化5.1 常见数据问题处理def clean_data(df): # 处理缺失值 df[rating] df[rating].fillna(0) # 统一书名格式 df[title] df[title].str.title() # 作者名标准化 df[author] df[author].apply( lambda x: re.sub(r^(by|and)\s, , x, flagsre.IGNORECASE) ) return df5.2 数据验证添加合理性检查def validate_row(row): if not row[title] or len(row[title]) 2: return False if row[rating] 0 or row[rating] 5: return False return True df df[df.apply(validate_row, axis1)]6. 持久化存储实现6.1 CSV导出def save_to_csv(df, filename): df.to_csv(filename, indexFalse, encodingutf-8-sig) # 添加BOM头解决Excel中文乱码问题 with open(filename, r, encodingutf-8-sig) as f: content f.read() f.seek(0) f.write(\ufeff content)6.2 SQLite存储def save_to_sqlite(df, db_namebooks.db): conn sqlite3.connect(db_name) df.to_sql(books, conn, if_existsreplace, indexFalse) conn.close()优化建议添加索引加速查询使用事务批量插入添加created_at时间戳7. 反爬策略应对7.1 请求头设置headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept-Language: en-US,en;q0.9, Referer: https://www.goodreads.com/, }7.2 代理与重试机制from time import sleep from random import uniform def safe_request(url, max_retries3): for _ in range(max_retries): try: sleep(uniform(1, 3)) # 随机延迟 response requests.get(url, headersheaders, timeout10) if response.status_code 200: return response except Exception as e: print(fRequest failed: {e}) return None8. 完整代码结构# goodreads_scraper.py class GoodreadsScraper: def __init__(self): self.base_url None self.session requests.Session() self.session.headers.update(headers) def scrape_list(self, list_url, pages1): books [] for page in range(1, pages1): url f{list_url}?page{page} response self.safe_request(url) if response: books.extend(self.parse_page(response.text)) return pd.DataFrame(books) def parse_page(self, html): soup BeautifulSoup(html, html.parser) books [] for book in soup.select(tr[itemtypehttp://schema.org/Book]): title, author self.extract_book_info(book) rating self.parse_rating(book.select_one(.rating).text) books.append({title: title, author: author, rating: rating}) return books # 包含之前定义的所有方法...9. 实际应用示例9.1 运行爬虫scraper GoodreadsScraper() df scraper.scrape_list( https://www.goodreads.com/list/show/175269, pages3 ) # 数据清洗 df clean_data(df) # 持久化 save_to_csv(df, best_books_2022.csv) save_to_sqlite(df, books.db)9.2 数据分析示例# 评分最高的10本书 top_books df.sort_values(rating, ascendingFalse).head(10) # 作者作品数量统计 author_counts df[author].value_counts().head(10)10. 常见问题与解决方案10.1 数据提取不完整可能原因页面结构变化 → 更新CSS选择器动态加载内容 → 考虑使用Selenium解决方案# 添加更健壮的选择器 title book.find(span, {itemprop: name}) or book.find(a, {class: bookTitle})10.2 编码问题处理方案response.encoding response.apparent_encoding # 自动检测编码10.3 数据库优化# 创建索引 conn sqlite3.connect(books.db) conn.execute(CREATE INDEX IF NOT EXISTS idx_author ON books(author)) conn.close()11. 项目扩展思路增加图书详情抓取通过图书URL获取更多元数据ISBN、页数等集成到阅读管理系统自动同步到Notion或Readwise可视化分析使用Matplotlib生成阅读趋势图定时任务每周自动更新书单数据这个爬虫项目最实用的部分是它的数据清洗逻辑特别是处理Goodreads上不规则的作者名字格式。在实际使用中我发现正则表达式的精细调整可以显著提高数据质量。比如处理J.K. Rowling, Mary GrandPré (Illustrator)这种情况需要特别设计模式匹配。