『亚马逊云科技产品测评』活动征文|使用scrapy抓取豆瓣图书数据

授权声明:本篇文章授权活动官方亚马逊云科技文章转发、改写权,包括不限于在 Developer Centre, 知乎,自媒体平台,第三方开发者媒体等亚马逊云科技官方渠道

背景

嘿朋友们,今天我要和大家分享一个有趣又实用的项目~我们来学习如何通过Scrapy在AWS Lightsail环境搭建基于豆瓣图书的爬虫系统!

最近我一直在研究 python 爬虫,公司需要很多数据源的数据,但是需要保证数据的安全性和高可用,很显然,如果我们自己从0开始搭建,那光在数据库上耗费的时间就会非常之多,可能还会导致我们的数据问题,一直在考虑使用云数据库,刚好看到AWS上的Lightsail可以一键部署一个高可用的数据库,效率非常高,而且也是有相对应套餐可以订购,作为刚刚白嫖了3个月的Lightsail,必须用上!

有了一个高性能高可用的数据库实例对于我们的爬取效率也会越来越高,能够使用分布式并发的特性将效率极大提升。

接下来我就和大家分享一下,从基础环境搭建,到Scrapy项目开发,再到AWS Lightsail部署的全过程。相信部署完这个系统后,你可以轻松高效地获取大量图书数据了!

Lightsail介绍

Amazon Lightsail 是AWS推出的一项面向开发者和小型企业的云计算服务。它简化了在AWS云平台上部署和管理虚拟租户的过程。

Lightsail提供预置的虚拟服务器实例,类似传统的VPS租用计划,但它完全管理于AWS云上,给开发者带来更高的弹性和安全性。使用Lightsail,我们可以通过简单的鼠标操作完成以下任务:

  • 选择硬件配置,目前支持CPU/内存配置从0.5G到8G不等的几款规格。
  • 创建公网IP地址或设置网络访问规则。
  • 选择操作系统,支持常用的Linux发行版和Windows系统。
  • 单击“启动实例”就可以快速获得虚拟主机资源。

此外,Lightsail提供方便的定价模式,只需要按月付费,不限使用量和流量。对于个人及小型项目来说,使用门槛低,成本效率高。

在这里插入图片描述

基于Lightsail爬取豆瓣图书数据

既然是体验,那必然要深入使用一下,这里我通过启动一台 Lightsail 实例,进行豆瓣图书的数据存储,使用的是 scrapy 框架,主要是对 top250 和评论的数据进行爬取,然后转存到Lightsail database instance,接下来跟着我的步骤一起来体验吧~

1、Lightsail 数据库实例构建

控制台地址为:https://lightsail.aws.amazon.com/ls/webapp/home/databases

在这里插入图片描述
点击 create database

在这里插入图片描述

在这里插入图片描述

这里我们采用最小配置就可以了,我的配置如下:

  • 版本:mysql8.0.35
  • 类型:standard
  • 架构:单节点
  • 算力:1GB 2vCPUs

不需要我们和原始一样自己在服务器上进行mysql下载和安装了,直接购买会自动生成一个高性能的云数据库,非常的便捷!!

2、通过客户端连接实例进行数据表构建

为了让客户端连接到数据库实例,我们需要先开放实例的外网连接,默认是连接不上的

在这里插入图片描述

这样就是打开的状态,我们在开发环境中就可以使用这个 主机+端口以及自己购买时的账号密码 进行连接

在这里插入图片描述

我们通过Navicat进行连接准备爬虫服务需要的数据库

  • 新建一个 douban 的数据库
    在这里插入图片描述

  • 建立 book 表和 book_comment 表

CREATE TABLE `book_comments` (
  `id` int NOT NULL AUTO_INCREMENT,
  `book_id` int NOT NULL,
  `book_name` varchar(100) NOT NULL,
  `username` varchar(30) NOT NULL,
  `rating` int DEFAULT NULL,
  `comment_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `useful_count` int DEFAULT '0',
  `content` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

CREATE TABLE `books` (
  `id` int NOT NULL AUTO_INCREMENT,
  `book_id` int DEFAULT NULL,
  `title` varchar(100) NOT NULL,
  `cover` varchar(200) DEFAULT NULL,
  `is_try_read` varchar(1) DEFAULT NULL,
  `author` varchar(30) NOT NULL,
  `publisher` varchar(50) DEFAULT NULL,
  `publish_date` varchar(30) DEFAULT NULL,
  `list_price` varchar(30) DEFAULT NULL,
  `rating` float DEFAULT NULL,
  `ratings_count` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=206 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

在这里插入图片描述

点击SQL窗口即可唤出,有了基础数据表之后,我们就需要进一步去搭建我们的开发环境,因为数据库很多参数、调优等AWS已经帮我们处理了,我们只需要使用即可

3、搭建爬虫项目

3.1 、基础scrapy框架搭建
  • 安装 scrapy
pip install scrapy 
  • 创建scrapy工程
 scrapy startproject 项目名称
  • 创建图书爬虫和评论爬虫
scrapy genspider 爬虫名 域名

这个时候,整个项目框架如下图所示:

在这里插入图片描述

  • 依赖管理

为了方便依赖的管理和项目的迁移,我定义了一份 requirements.txt 文件,内容如下:

scrapy
pymysql
  • 安装依赖
pip install -r requirement.txt

scrapy 就是我们的爬虫框架,pymysql 是用来连接我们的 Lightsail Mysql 数据库的

这样就完成了最基础的框架搭建,然后我们主要对以下几个文件进行开发和修改:

  • book_spider.py 图书的爬虫(需开发)
  • book_comment_spider.py 图书评论的爬虫(需开发)
  • items.py ORM文件(需开发)
  • pipelines.py 管道文件,用来将数据存储到 AWS数据库 (需开发)
  • settings.py 配置文件(需要修改)

我们首先将 Lightsail Mysql 集成到项目中

3.2 、根据数据表构建ORM

在 items.py 中增加两个模型映射,代码如下:


class DoubanBooksItem(scrapy.Item):
    book_id = scrapy.Field()
    title = scrapy.Field()
    cover_link = scrapy.Field()
    is_try_read = scrapy.Field()
    author = scrapy.Field()
    publisher = scrapy.Field()
    publish_date = scrapy.Field()
    price = scrapy.Field()
    rating = scrapy.Field()
    rating_count = scrapy.Field()


class DoubanBookCommentItem(scrapy.Item):
    book_id = scrapy.Field()
    book_name = scrapy.Field()
    username = scrapy.Field()
    rating = scrapy.Field()
    comment_time = scrapy.Field()
    useful_count = scrapy.Field()
    content = scrapy.Field()

3.3 、集成Lightsail Mysql
  • 增加数据库配置

在这里插入图片描述

在文件最尾部增加 DATABASE 配置:

DATABASE = {
   'host': 'ls-f78481475a51804987f6ff06db2e3d675421989d.c8ugxo23bwey.ap-northeast-2.rds.amazonaws.com',
   'port': 3306,
   'user': 'dbmasteruser',
   'passwd': '4(Be}Sm>hjxbgw0*<C+TeTC&*Y?p#[lZ',
   'db': 'douban',
   'charset': 'utf8',
}
  • 增加 pipelines 配置

在这里插入图片描述

ITEM_PIPELINES = {
    'douban_books.pipelines.DoubanBookCommentAWSPipeline': 1,
}

这是我们下一步需要开发的 pipeline

3.4 、开发 DoubanBookCommentAWSPipeline

因为我们有两个不同的 item,所以我们会在 process_item 中进行判断

from twisted.enterprise import adbapi

from douban_books.items import DoubanBooksItem, DoubanBookCommentItem
from douban_books.settings import DATABASE

class DoubanBookCommentAWSPipeline:

    def __init__(self):
        self.conn = adbapi.ConnectionPool('pymysql', **DATABASE)

    def do_insert_book(self, tx, item):
        # 执行插入操作
        tx.execute("""insert into books (book_id, title, cover, is_try_read, author, publisher, publish_date, list_price, rating, ratings_count) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""", (item['book_id'], item['title'], item['cover_link'], item['is_try_read'], item['author'], item['publisher'], item['publish_date'], item['price'], item['rating'], item['rating_count']))

    def do_insert_book_comment(self, tx, item):
        # 执行插入操作
        tx.execute("""insert into books (book_id, book_name, username, rating, comment_time, useful_count, content) values (%s,%s,%s,%s,%s,%s,%s)""", (item['book_id'], item['book_name'], item['username'], item['rating'], item['comment_time'], item['useful_count'], item['content']))

    def process_item(self, item, spider):
        if isinstance(item, DoubanBooksItem):
            print('开始写入 book')
            query = self.conn.runInteraction(self.do_insert_book, item)
            query.addErrback(self.handle_error)
        elif isinstance(item, DoubanBookCommentItem):
            print(item)
            print('开始写入 book item')

            query = self.conn.runInteraction(self.do_insert_book_comment, item)
            query.addErrback(self.handle_error)

        return item

    def handle_error(self, failure):
        # 处理异步插入的错误
        print(failure)
3.5 、开发豆瓣图书爬虫

这里我们主要爬取 top250 的图书,爬虫的数据会最后会通过 pipeline 转存到 Lightsail Mysql

import scrapy
from douban_books.items import DoubanBooksItem
import re


class DoubanBookSpider(scrapy.Spider):
    name = 'douban_book_spider'
    start_urls = ['https://book.douban.com/top250']

    def parse(self, response):
        # 解析书籍信息
        for book_tr in response.css('tr.item'):
            item = DoubanBooksItem()
            # 提取书籍URL
            book_url = book_tr.css('div.pl2 > a::attr(href)').get()
            # 提取书籍ID
            item['book_id'] = book_url.split('/')[-2] if book_url else None

            item['title'] = book_tr.css('div.pl2 a::text').get().strip()
            item['cover_link'] = book_tr.css('td a.nbg img::attr(src)').get()
            item['is_try_read'] = "是" if book_tr.css('div.pl2 img[title="可试读"]') else "否"

            # 提取作者、出版社、发行日期和价格的信息
            details = book_tr.css('p.pl::text').get().strip().split(' / ')
            item['author'] = details[0]
            item['publisher'] = details[-3]
            item['publish_date'] = details[-2]
            item['price'] = details[-1]

            item['rating'] = book_tr.css('span.rating_nums::text').get()
            rating_count_text = book_tr.css('span.pl::text').get()
            item['rating_count'] = re.search(r'(\d+)人评价', rating_count_text).group(1) if rating_count_text else None
            yield item

        # 翻页处理
        next_page = response.css('span.next a::attr(href)').get()
        if next_page:
            yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse)

现在我们可以对这个爬虫进行运行测试爬取

scrapy crawl douban_book_spider

在这里插入图片描述

  • 通过Navicat查看数据是否正常写入

在这里插入图片描述

3.6 、开发豆瓣图书评论爬虫

这里我们要通过 Lightsail Mysql 获取所有爬取的图书的 id,进行评论获取

import pymysql
import scrapy
import json
from douban_books.items import DoubanBookCommentItem
from douban_books.settings import DATABASE


class DoubanBookCommentSpider(scrapy.Spider):
    name = 'douban_book_comment_spider'

    db = pymysql.connect(**DATABASE)
    # 使用Cursor执行SQL
    cursor = db.cursor()
    sql = "SELECT book_id FROM books"
    cursor.execute(sql)
    # 获取结果
    results = cursor.fetchall()


    # 提取book_id
    book_ids = [result[0] for result in results]
    # Step 2: Generate start_urls from book.csv
    start_urls = [f'https://book.douban.com/subject/{book_id}/' for book_id in book_ids]

    def parse(self, response):
        self.logger.info(f"Parsing: {response.url}")

        # Extract music name
        book_title = response.css('h1 span::text').get()
        print(book_title)

        # Construct the initial comments URL
        book_id = response.url.split("/")[4]
        comments_url = f'https://book.douban.com/subject/{book_id}/comments/?start=0&limit=20&status=P&sort=new_score&comments_only=1'
        print(comments_url)
        yield scrapy.Request(url=comments_url, callback=self.parse_comments, meta={'book_title': book_title, 'book_id': book_id})

    def parse_comments(self, response):
        # Extract the HTML content from the JSON data
        html_content = response.json()['html']
        selector = scrapy.Selector(text=html_content)
        book_name = response.meta['book_title']
        book_id = response.meta['book_id']


        data = json.loads(response.text)
        print(selector.css('li.comment-item'))
        # 解析评论
        for comment in selector.css('li.comment-item'):
            item = DoubanBookCommentItem()
            item['book_id'] = book_id
            item['book_name'] = book_name
            item['username'] = comment.css('a::attr(title)').get()
            item['rating'] = comment.css('.comment-info span.rating::attr(title)').get()
            # rating_class = comment.css('span.rating::attr(class)').get()
            # item['rating'] = self.parse_rating(rating_class) if rating_class else None
            item['comment_time'] = comment.css('span.comment-info > a.comment-time::text').get()

            # item['comment_time'] = comment.css('span.comment-time::text').get()
            item['useful_count'] = comment.css('span.vote-count::text').get()
            item['content'] = comment.css('span.short::text').get()
            yield item

        book_id = response.url.split("/")[4]
        base_url = f"https://book.douban.com/subject/{book_id}/comments/"
        next_page = selector.css('#paginator a[data-page="next"]::attr(href)').get()
        if next_page:
            next_page_url = base_url + next_page + '&comments_only=1'
            yield scrapy.Request(url=next_page_url, callback=self.parse_comments, meta={'book_title': book_name, 'book_id': book_id})
  • 运行评论爬虫
scrapy crawl douban_book_comment_spider

在这里插入图片描述
这样就完成了整个爬虫服务的开发了,通过整个 Lightsail Mysql 的体验,还是非常不错的,我们自己不用考虑可用性和性能,AWS天然就帮我们完成了这些工种,极大地提高了工作的效率!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐