数据采集与融合技术第三次作业
基于Scrapy的网络数据爬取实战:图片、股票与外汇信息
我的getee仓库链接 https://gitee.com/ayinazi/crawl_project
作业3代码链接 https://gitee.com/ayinazi/crawl_project/tree/master/作业3
信息 | 详情 |
---|---|
课程相关 | 2024数据采集与融合技术 |
学号 | 102202124 |
姓名 | 阿依娜孜 |
目录
前言
使用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
这里,我们设置了name
、allowed_domains
和start_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
表中创建的字段类型,发现是count
和zfcount
字段类型在设置时没有充分考虑数据的实际情况。将它们调整为合适的类型(如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框架在不同类型数据爬取和存储方面有了更全面深入的理解和掌握,为今后处理更复杂的数据爬取任务奠定了坚实的基础。希望我的这些经验分享能对大家有所帮助,让大家在数据爬取的道路上少走一些弯路。