数据采集与融合技术第三次作业

基于Scrapy的网络数据爬取实战:图片、股票与外汇信息

我的getee仓库链接 https://gitee.com/ayinazi/crawl_project
作业3代码链接 https://gitee.com/ayinazi/crawl_project/tree/master/作业3

信息 详情
课程相关 2024数据采集与融合技术
学号 102202124
姓名 阿依娜孜

目录

  1. 前言
  2. 作业一:爬取气象网图片(单线程与多线程)
  3. 作业二:爬取股票信息并存储到MySQL数据库
  4. 作业三:爬取外汇网站数据并存储到MySQL数据库

前言

使用Scrapy框架完成的三个有趣的数据爬取作业,涵盖网站图片、股票信息和外汇数据的爬取。

作业一:爬取气象网图片(单线程与多线程)

步骤一:创建Scrapy项目

首先,确保已经安装了Scrapy库。若未安装,可在命令行中执行pip install scrapy。接着,在你期望创建项目的目录下,于命令行中输入scrapy startproject weather_image_crawler。这样就创建了一个名为weather_image_crawler的项目,其目录结构如下:

weather_image_crawler/
    scrapy.cfg
    weather_image_crawler/
        __init__.py
        items.py
        middlewares.py
        pipelines.py
        settings.py
        spiders/
            __init__.py

步骤二:定义Item

items.py文件中定义用于存储图片信息的WeatherImageItem类。我们重点关注图片的URL,代码如下:

import scrapy

class WeatherImageItem(scrapy.Item):
    image_urls = scrapy.Field()

步骤三:编写单线程爬取的Spider

spiders目录下创建weather_single_thread_spider.py文件,编写WeatherSingleThreadSpider类,它继承自scrapy.Spider。代码如下:

import scrapy
from weather_image_crawler.items import WeatherImageItem

class WeatherSingleThreadSpider(scrapy.Spider):
    name = "weather_single_thread"
    allowed_domains = ["weather.com.cn"]
    start_urls = ["http://www.weather.com.cn"]

    def parse(self, response):
        item = WeatherImageItem()
        # 使用xpath或css选择器来提取图片的URL
        image_urls = response.xpath('//img/@src').getall()
        item['image_urls'] = image_urls
        for url in image_urls:
            self.logger.info(f"Downloading image from: {url}")
        yield item

这里,我们设置了nameallowed_domainsstart_urls等属性,并在parse方法中通过XPath表达式提取页面中的所有图片URL,将其存储在WeatherImageItem中,同时在控制台输出每个图片的下载URL。

步骤四:编写多线程爬取的Spider

创建weather_multi_thread_spider.py文件,编写WeatherMultiThreadSpider类,代码如下:

import scrapy
from weather_image_crawler.items import WeatherImageItem

class WeatherMultiThreadSpider(scrapy.Spider):
    name = "weather_multi_thread"
    allowed_domains = ["weather.com.cn"]
    start_urls = ["http://www.weather.com.cn"]

    def parse(self, response):
        item = WeatherImageItem()
        image_urls = response.xpath('//img/@src').getall()
        item['image_urls'] = image_urls
        for url in image_urls:
            self.logger.info(f"Downloading image from: {url}")
        yield item

这里的多线程Spider代码和单线程的在结构上基本相同,因为Scrapy会自动处理异步请求,实现高效的并发处理,类似于多线程的效果。

步骤五:设置图片存储管道

为了将下载的图片存储到images子文件夹中,需要在pipelines.py文件中设置图片存储管道。代码如下:

import scrapy
from scrapy.pipelines.images import ImagesPipeline

class WeatherImagePipeline(ImagesPipeline):

    def get_media_requests(self, item, info):
        for image_url in item['image_urls']:
            yield scrapy.Request(image_url)

    def item_completed(self, results, item, info):
        return item

然后在settings.py文件中,配置以下内容:

# 设置图片存储路径
IMAGES_STORE = 'images'
# 启用图片管道
ITEM_PIPELINES = {
    'weather_image_crawler.pipelines.WeatherImagePipeline': 300,}

步骤六:运行爬取及结果展示

在项目根目录下,可以通过以下命令分别运行单线程和多线程的爬取:

  • 单线程爬取:scrapy crawl weather_single_thread
  • 多线程爬取(实际上是利用Scrapy的异步机制实现高效并发):scrapy crawl weather_multi_thread

运行后,控制台会输出每个图片的下载URL信息,并且下载的图片会被存储到images子文件夹中。

遇到的困难及解决方案

  • 困难:在多线程爬取时,遇到了图片下载不完全的情况,部分图片URL无法正确获取。
  • 解决方案:经过排查,发现是网络请求过于频繁导致部分请求被拒绝。通过设置DOWNLOAD_DELAY参数,适当增加请求间隔时间,解决了这个问题。

心得体会

通过这个作业,我深刻体会到了Scrapy框架的强大和灵活。单线程和多线程(异步)爬取的实现让我对网络请求和数据处理有了更深入的理解。同时,处理图片存储和URL提取的过程也让我更加熟悉了XPath的使用。

作业二:爬取股票信息并存储到MySQL数据库

项目结构与核心代码介绍

  • 项目结构
stock_scraper/
    │
    ├── spiders/
    │   └── stock_spider.py 
    │
    ├── items.py 
    ├── pipelines.py 
    └── settings.py 
  • 核心代码
    • items.py中,定义StockItem类来存储股票信息:
import scrapy
class StockItem(scrapy.Item):
    id = scrapy.Field()
    code = scrapy.Field()
    name = scrapy.Field()
    latest_price = scrapy.Field()
    change_degree = scrapy.Field()
    change_amount = scrapy.Field()
    count = scrapy.Field()
    money = scrapy.Field()
    zfcount = scrapy.Field()
    highest = scrapy.Field()
    lowest = scrapy.Field()
    today = scrapy.Field()
    yestoday = scrapy.Field()
- 在`stock_spider.py`中,`StockSpider`类用于爬取数据:
import scrapy
import json
from stock_scraper.items import StockItem

class StockSpider(scrapy.Spider):
    name = 'stock_spider'
    allowed_domains = ['eastmoney.com']
    start_urls = ["http://38.push2.eastmoney.com/api/qt/clist/get?cb=..."]

    def parse(self, response):
        data = json.loads(response.text[response.text.find('{'):response.rfind('}') + 1])
        for stock in data['data']['diff']:
            item = StockItem()
            item['code'] = stock['f12']
            # 其他字段赋值类似,此处省略部分代码
            yield item
- 在`pipelines.py`中,`StockPipeline`类负责与MySQL数据库交互:
import pymysql
from pymysql import OperationalError, MySQLError

class StockPipeline:
    def open_spider(self, spider):
        try:
            self.conn = pymysql.connect(
                host=spider.settings.get('MYSQL_HOST'),
                port=spider.settings.get('MYSQL_PORT'),
                user=spider.settings.get('MYSQL_USER'),
                password=spider.settings.get('MYSQL_PASSWORD'),
                db=spider.settings.get('MYSQL_DB'),
                charset=spider.settings.get('MYSQL_CHARSET'),
                cursorclass=pymysql.cursors.DictCursor
            )
            self.cursor = self.conn.cursor()
        except OperationalError as e:
            spider.logger.error(f"Could not connect to MySQL: {e}")
            raise

    def process_item(self, item, spider):
        try:
            sql = """
            INSERT INTO stocks (code, name, latest_price, change_degree, change_amount, count, money, zfcount, highest, lowest, today, yestoday)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """
            self.cursor.execute(sql, (
                item['code'], item['name'], item['latest_price'], item['change_degree'],
                item['change_amount'], item['count'], item['money'], item['zfcount'],
                item['highest'], item['lowest'], item['today'], item['yestoday']
            ))
            self.conn.commit()
        except MySQLError as e:
            spider.logger.error(f"Failed to insert item: {e}")
            self.conn.rollback()

        return item

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()
- 在`settings.py`中配置数据库和Scrapy相关设置:
# Database settings
MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306
MYSQL_USER = 'zz'
MYSQL_PASSWORD = '020026'
MYSQL_DB = 'stock_data'

# Scrapy settings
BOT_NAME = 'stock_scraper'
SPIDER_MODULES = ['stock_scraper.spiders']
NEWSPIDER_MODULE = 'stock_scraper.spiders'
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
    'stock_scraper.pipelines.StockPipeline': 300,
}
CONCURRENT_REQUESTS = 32
FEED_EXPORT_ENCODING = 'utf-8'

创建MySQL数据库表

在mysql终端上创建stock_data数据库和stocks表,具体创建语句如下:

mysql> USE stock_data;
Database changed
mysql> CREATE TABLE stocks (
    code VARCHAR(20),  -- 假设股票代码长度一般不超过20个字符,可根据实际调整
    name VARCHAR(100),  -- 股票名称,长度可按需设置,这里假设最长100个字符
    latest_price DECIMAL(10, 2),  -- 最新价格,共10位,小数点后2位,可根据价格范围调整精度
    change_degree DECIMAL(5, 2),  -- 涨跌幅,共5位,小数点后2位,可根据实际情况调整
    change_amount DECIMAL(10, 2),  -- 涨跌额,共10位,小数点后2位,类似最新价格设置
    count BIGINT,  -- 成交量,根据股票成交量可能较大的特点,使用BIGINT类型
    money DECIMAL(15, 2),  -- 成交额,共15位,小数点后2位,可根据实际金额范围调整
    zfcount BIGINT,  -- 主力资金流向数量(这里不太明确具体含义,可根据实际调整类型),暂设为BIGINT
    highest DECIMAL(10, 2),  -- 最高价,共10位,小数点后2位
    lowest DECIMAL(10, 2),  -- 最低价,共10位,小数点后2位
    today VARCHAR(20),  -- 今日日期或其他相关时间信息,假设格式如'2024-11-06',可根据实际调整格式和类型
    yestoday VARCHAR(20)  -- 昨日日期或其他相关时间信息,同今日设置
);

运行结果及展示

在mysql终端上查看爬取到的股票数据,数据按照我们设计的表结构存储。

遇到的困难及解决方案

  • 困难:在将数据插入MySQL数据库时,遇到了数据类型不匹配的问题,导致插入失败。
  • 解决方案:仔细检查了items.py中定义的字段类型和mysql表中创建的字段类型,发现是countzfcount字段类型在设置时没有充分考虑数据的实际情况。将它们调整为合适的类型(如BIGINT)后,问题得到解决。

心得体会

这个作业让我对数据的序列化输出有了更清晰的认识。将爬取到的股票信息准确地存储到MySQL数据库需要对数据类型和结构有精确的把握。同时,也让我意识到在处理复杂数据结构时,要更加细心地设计和调试。

作业三:爬取外汇网站数据并存储到MySQL数据库

项目结构与核心代码介绍

  • 项目结构
forex_scraper/
    │
    ├── spiders/
    │   └── boc_forex_spider.py 
    │
    ├── items.py 
    ├── pipelines.py 
    └── settings.py 
  • 核心代码
    • items.py中定义ForexRateItem类:
import scrapy
class ForexRateItem(scrapy.Item):
    currency = scrapy.Field()
    tbp = scrapy.Field()
    cbp = scrapy.Field()
    tsp = scrapy.Field()
    csp = scrapy.Field()
    time = scrapy.Field()
- 在`boc_forex_spider.py`中,`BocForexSpider`类用于爬取外汇数据:
import scrapy
from forex_scraper.items import ForexRateItem

class BocForexSpider(scrapy.Spider):
    name = 'boc_forex'
    allowed_domains = ['boc.cn']
    start_urls = ['https://www.boc.cn/sourcedb/whpj/']

    def parse(self, response):
        rows = response.xpath('//div[@class="BOC_main"]/div[1]/div[2]/table//tr')[1:] # 跳过表头
        for row in rows:
            item = ForexRateItem()
            item['currency'] = row.xpath('./td[1]/text()').get()
            item['tbp'] = row.xpath('./td[2]/text()').get()
            item['cbp'] = row.xpath('./td[3]/text()').get()
            item['tsp'] = row.xpath('./td[4]/text()').get()
            item['csp'] = row.xpath('./td[5]/text()').get()
            item['time'] = row.xpath('./td[last()]/text()').get()
            yield item
- 在`pipelines.py`中,`MySQLPipeline`类负责与MySQL数据库交互:
import pymysql
from pymysql import OperationalError, MySQLError

class MySQLPipeline(object):
    def open_spider(self, spider):
        try:
            self.conn = pymysql.connect(
                host=spider.settings.get('MYSQL_HOST'),
                port=spider.settings.get('MYSQL_PORT'),
                user=spider.settings.get('MYSQL_USER'),
                password=spider.settings.get('MYSQL_PASSWORD'),
                db=spider.settings.get('MYSQL_DB'),
                charset=spider.settings.get('MYSQL_CHARSET'),
                cursorclass=pymysql.cursors.DictCursor
            )
            self.cursor = self.conn.cursor()
        except OperationalError as e:
            spider.logger.error(f"无法连接到 MySQL: {e}")
            raise

    def process_item(self, item, spider):
        try:

settings.py中配置MySQL相关设置:

# Database settings
MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306
MYSQL_USER = 'zz'
MYSQL_PASSWORD = '020026'
MYSQL_DB = 'forex_data'

创建MySQL数据库表与运行结果查看

在MySQL终端上创建forex_data数据库和forex_rates表,创建语句如下:

mysql> CREATE TABLE forex_rates (
    currency VARCHAR(50),
    tbp DECIMAL(10, 2),
    cbp DECIMAL(10, 2),
    tsp DECIMAL(10, 2),
    csp DECIMAL(10, 2),
    time VARCHAR(20)
);

运行爬虫后,可以在mysql上查看爬取到的数据。

遇到的困难及解决方案

  • 困难:在使用XPath提取外汇数据时,发现提取到的数据有一些乱码和格式错误。
  • 解决方案:检查网页的编码格式,发现与默认的解析方式有冲突。通过在settings.py中设置FEED_EXPORT_ENCODING参数为网页的正确编码格式,解决了数据乱码问题。对于格式错误,进一步检查XPath表达式,发现是部分元素的定位不够精确,调整XPath表达式后,数据格式正确。

心得体会

这次作业让我在处理外汇数据爬取时遇到了新的挑战,尤其是编码和数据格式问题。这让我明白在实际的数据爬取过程中,要充分考虑到数据来源的各种特性,不能仅仅依赖默认的设置。同时,也进一步提升了我对Scrapy和XPath的使用能力,以及解决实际问题的能力。通过这三个作业,我对Scrapy框架在不同类型数据爬取和存储方面有了更全面深入的理解和掌握,为今后处理更复杂的数据爬取任务奠定了坚实的基础。希望我的这些经验分享能对大家有所帮助,让大家在数据爬取的道路上少走一些弯路。

posted on 2024-11-07 00:02  zzsthere  阅读(0)  评论(0编辑  收藏  举报