数据采集与融合实践第三次作业
这个作业属于哪个课程 | 2024数据采集与融合班级 |
---|---|
本次作业的码云 | fufubuff_python爬虫实践作业3 |
这次作业的链接 | 数据采集与融合第二次作业 |
学号姓名 | 102202141黄昕怡 |
作业内容概况
作业一
作业要求
开发环境
- 编程语言:Python 3.8
- 爬虫框架:Scrapy 2.5
- 开发工具:PyCharm 2021.1
- 操作系统:Windows 10
实现步骤
步骤一:环境准备
首先,确保已安装 Python 和 Scrapy。可以通过以下命令安装 Scrapy:
pip install scrapy
步骤创建 Scrapy 项目
使用 Scrapy 的命令行工具创建一个新的项目:
scrapy startproject weather_scraper cd weather_scraper
步骤三:定义 Item
编辑 weather_scraper/items.py 文件,定义一个 WeatherItem 用于存储图片的 URL:
import scrapy class WeatherItem(scrapy.Item): img_url = scrapy.Field() pass
步骤四:编写爬虫
在 weather_scraper/spiders 目录下创建 scrapy_weather.py,编写爬虫逻辑:
# scrapy_weather.py import scrapy from urllib.parse import urljoin from weather_scraper.items import WeatherItem class ScrapyWeatherSpider(scrapy.Spider): name = "scrapy_weather" allowed_domains = ["weather.com.cn"] start_urls = ["http://www.weather.com.cn"] def parse(self, response): # Extract all image src attributes imgs = response.css('img::attr(src)').getall() for img_src in imgs: # Ensure the image URL is absolute img_url = response.urljoin(img_src) # Validate the URL if img_url.startswith('http'): item = WeatherItem() item['img_url'] = img_url yield item else: self.logger.warning(f'Invalid image URL skipped: {img_url}')
步骤五:配置图片管道
在 weather_scraper/pipelines.py 中配置图片管道,以便下载并保存图片:
from scrapy.pipelines.images import ImagesPipeline import scrapy class WeatherImagesPipeline(ImagesPipeline): def get_media_requests(self, item, info): img_url = item.get('img_url') if img_url: yield scrapy.Request(url=img_url) else: self.logger.warning(f'Missing img_url in item: {item}') def file_path(self, request, response=None, info=None, *, item=None): image_name = request.url.split('/')[-1] return image_name
在 settings.py 中进行以下配置:
`BOT_NAME = 'weather_scraper' SPIDER_MODULES = ['weather_scraper.spiders'] NEWSPIDER_MODULE = 'weather_scraper.spiders' # 用户代理 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' # 是否遵守robots.txt规则 ROBOTSTXT_OBEY = False # 并发请求 CONCURRENT_REQUESTS = 32 # 图片管道激活 ITEM_PIPELINES = { 'weather_scraper.pipelines.WeatherImagesPipeline': 300, } # 媒体允许重定向 MEDIA_ALLOW_REDIRECTS = True # 图片存储路径 IMAGES_STORE = './images' DOWNLOAD_DELAY = 1 LOG_LEVEL = 'DEBUG' REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7" TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor" FEED_EXPORT_ENCODING = "utf-8"
步骤六:运行爬虫
在项目根目录下运行爬虫,开始抓取图片:
scrapy crawl scrapy_weather
实验结果
遇到的困难和解决方案
在本次作业中,我遇到了以下困难,并通过分析和调整代码成功解决。
1. ValueError: Missing scheme in request url
错误详情:
在运行爬虫时,控制台出现以下错误信息:
ValueError: Missing scheme in request url: h
原因分析:
- 该错误提示请求的 URL 缺少协议部分(如
http://
或https://
)。 - 这是由于在爬虫中提取的图片链接为相对路径,未转换为绝对 URL,导致
scrapy.Request
无法识别。
解决方案:
- 使用
response.urljoin()
方法: 该方法可以将相对路径转换为包含协议和域名的完整绝对路径。 - 更新爬虫代码:
# scrapy_weather.py import scrapy from weather_scraper.items import WeatherItem class ScrapyWeatherSpider(scrapy.Spider): name = "scrapy_weather" allowed_domains = ["weather.com.cn"] start_urls = ["http://www.weather.com.cn"] def parse(self, response): # 提取所有图片的 src 属性 imgs = response.css('img::attr(src)').getall() for img_src in imgs: # 将相对路径转换为绝对 URL img_url = response.urljoin(img_src) # 检查 URL 的有效性 if img_url.startswith('http'): item = WeatherItem() item['img_url'] = img_url yield item else: self.logger.warning(f'Invalid image URL skipped: {img_url}')
效果验证: 修改后,错误消失,图片 URL 能够正确解析并下载。
2. KeyError: 'WeatherItem does not support field: img_url'
错误详情:
在运行爬虫时,出现以下错误信息:
KeyError: 'WeatherItem does not support field: img_url'
原因分析:
该错误是由于在 WeatherItem 类中未定义 img_url 字段,但在爬虫和管道中使用了 item['img_url'],导致字段不存在的错误。
解决方案:
在 items.py 中添加 img_url 字段:
import scrapy class WeatherItem(scrapy.Item): img_url = scrapy.Field()
确保字段名称一致: 检查爬虫和管道代码,确认使用的字段名称与 items.py 中定义的一致。
效果验证: 修改后,KeyError 错误消失,爬虫能够正常运行并处理图片链接。
心得体会
通过本次作业,我深入学习了 Scrapy 框架的基本使用方法,包括如何创建项目、定义 Item、编写爬虫和配置管道。实践过程中,我更加熟悉了爬虫的配置和优化,尤其是如何处理和保存网络上的图片资源。
在编写爬虫时,我注意到需要处理图片 URL 的完整性,使用 response.urljoin() 可以确保获取到完整的图片链接。此外,配置图片管道时,需要正确设置 IMAGES_STORE 路径,以确保图片能够正确保存。
这次实践加深了我对 Web 爬虫技术的理解,为未来的数据采集和分析工作打下了坚实的基础。
我深刻体会到代码细节和一致性的重要性。主要有以下收获:
1.字段命名的一致性: 在 Scrapy 中,Item、Spider 和 Pipeline 之间的数据传递依赖于字段名称的一致性。字段命名不一致会导致数据无法正确传递和处理。
2.URL 的完整性: 在处理网页爬取时,提取的链接可能是相对路径,需要转换为绝对路径才能正确访问。使用 response.urljoin() 可以方便地完成这一转换。
3.错误调试的方法: 当遇到错误时,通过仔细阅读错误信息,定位到具体的代码行,然后结合 Scrapy 的文档和日志,可以有效地找到问题的根源。
4.日志的重要性: 合理地使用日志功能,可以在调试时提供有价值的信息,帮助快速定位和解决问题。
通过本次作业,我成功实现了一个功能完整的 Scrapy 爬虫,不仅掌握了 Scrapy 的核心技术,还增强了对 Python 编程的应用能力。未来,我计划进一步探索 Scrapy 的高级特性,如中间件的使用、动态内容的处理等。
作业二
作业要求
实现步骤
项目结构
stock_scraper/ │ ├── spiders/ │ └── stock_spider.py # 爬虫逻辑实现 │ ├── items.py # 定义抓取数据的结构 ├── pipelines.py # 数据处理和保存逻辑 └── settings.py # 配置文件
核心代码
items.py
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
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
在mysql终端上创建
Enter password: ********* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 10 Server version: 8.0.40 MySQL Community Server - GPL Copyright (c) 2000, 2024, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> CREATE DATABASE IF NOT EXISTS fufubuff; Query OK, 1 row affected (0.00 sec) mysql> USE fufubuff; Database changed mysql> mysql> CREATE TABLE IF NOT EXISTS stocks ( -> id INT AUTO_INCREMENT PRIMARY KEY, -> code VARCHAR(255), -> name VARCHAR(255), -> latest_price FLOAT, -> change_degree FLOAT, -> change_amount FLOAT, -> count BIGINT, -> money FLOAT, -> zfcount FLOAT, -> highest FLOAT, -> lowest FLOAT, -> today FLOAT, -> yestoday FLOAT -> ); Query OK, 0 rows affected (0.01 sec) mysql> GRANT ALL PRIVILEGES ON fufubuff.* TO 'root'@'localhost' IDENTIFIED BY 'hhxxyy120'; FLUSH PRIVILEGES; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IDENTIFIED BY 'hhxxyy120'' at line 1 Query OK, 0 rows affected (0.01 sec)BOT_NAME = 'stock_scraper' SPIDER_MODULES = ['stock_scraper.spiders']
pipelines.py
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
# Database settings MYSQL_HOST = 'localhost' MYSQL_PORT = 3306 MYSQL_USER = 'fufu' MYSQL_PASSWORD = 'hhxxyy120' MYSQL_DB = 'fufubuff' MYSQL_CHARSET = 'utf8mb4' # 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终端上查看
导出成csv文件
在导出的路径查看
遇到的困难及解决方案
困难 1. 数据库连接问题和数据导出问题
我遇到了无法连接到 MySQL 数据库的问题。错误信息表明用户名和密码被拒绝。通过检查和修改 settings.py 中的数据库配置,并确保数据库用户具有适当的访问权限,我解决了这个问题。
在尝试使用 INTO OUTFILE 将数据导出时,由于 MySQL 的 secure_file_priv 配置,我无法将文件导出到预期路径。通过查询允许的目录并调整输出文件的路径,我解决了这个问题。
困难 2: 分页处理
问题描述: 需要处理数据分页以抓取更多数据。
解决方案: 实现自动翻页逻辑,在 parse 方法中检查是否存在下一页,并递归调用解析方法。
心得体会
面对动态网页的数据抓取
动态数据抓取的挑战:本次在作业中中的一个主要挑战来自于东方财富网的动态网页。在最初尝试爬取数据时,我注意到通过常规的 HTTP 请求无法获取到页面上显示的实时数据。这是因为网页内容是通过 JavaScript 动态生成的,而这些数据在初次页面加载时并不包含在 HTML 中。
解决方法的探索:为了解决这个问题,我采用了几种不同的策略:
抓包分析:我使用了浏览器的开发者工具对网络请求进行了深入的抓包分析。通过仔细查看网络请求的预览信息,并使用 Ctrl+F 搜索具体的股票数据,我最终找到了包含所需数据的 AJAX 请求。这个过程需要耐心和细致,因为数据隐藏在了响应内容的末尾,一开始很容易被忽略。
Scrapy 结合 Selenium:考虑到网站的数据动态加载问题,我也了解并尝试使用了 Selenium 来直接从浏览器中获取完整渲染后的页面数据。这种方法虽然处理起来较为繁琐,但能有效解决 JavaScript 动态渲染的问题。
技术的深化理解:通过这次项目,我不仅学习了如何使用 Scrapy 框架抓取静态和动态网页数据,还对如何结合 Selenium 处理复杂的网页渲染有了更深的理解和实践。此外,抓包技术对于理解 Web 应用的数据交互流程也是非常宝贵的学习经验。
深入理解数据库操作:
通过这次实验,我不仅学会了如何在 MySQL 中创建用户和数据库,还学会了如何设置和修改用户权限,这些都是实际工作中非常实用的技能。特别是在处理 secure_file_priv 的问题时,我了解到了 MySQL 安全设置的重要性及其配置方式,这在未来的数据库管理工作中将非常有帮助。
在项目过程中,我遇到了多个挑战,如数据库连接错误、权限配置问题等。每解决一个问题,我都感到自己的问题解决能力在提升。我学会了如何分析错误日志,如何查找和利用在线资源(如官方文档和技术论坛)来解决问题。在解决数据导出的问题中,我更加明白了数据安全配置的重要性。了解到不恰当的权限设置可能会导致数据泄露或被恶意访问,这让我在未来的工作中会更加注重数据的安全和保护。
作业三
作业要求
实现步骤
项目结构
forex_scraper/ │ ├── spiders/ │ └── boc_forex_spider.py # 爬虫逻辑实现 │ ├── items.py # 定义抓取数据的结构 ├── pipelines.py # 数据处理和保存逻辑 └── settings.py # 配置文件
核心代码
items.py
import scrapy class ForexRateItem(scrapy.Item): currency = scrapy.Field() tbp = scrapy.Field() cbp = scrapy.Field() tsp = scrapy.Field() csp = scrapy.Field() time = scrapy.Field()
pipelines.py
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: sql = """ INSERT INTO forex_rates (currency, tbp, cbp, tsp, csp, time) VALUES (%s, %s, %s, %s, %s, %s) """ self.cursor.execute(sql, ( item['currency'], item['tbp'], item['cbp'], item['tsp'], item['csp'], item['time'] )) self.conn.commit() except MySQLError as e: spider.logger.error(f"插入数据失败: {e}") self.conn.rollback() return item def close_spider(self, spider): self.cursor.close() self.conn.close()
settings.py
# Database settings MYSQL_HOST = 'localhost' MYSQL_PORT = 3306 MYSQL_USER = 'fufu' MYSQL_PASSWORD = 'hhxxyy120' MYSQL_DB = 'fufubuff' MYSQL_CHARSET = 'utf8mb4' # Scrapy settings BOT_NAME = 'forex_scraper' SPIDER_MODULES = ['forex_scraper.spiders'] NEWSPIDER_MODULE = 'forex_scraper.spiders' ROBOTSTXT_OBEY = False ITEM_PIPELINES = { 'forex_scraper.pipelines.MySQLPipeline': 300, } CONCURRENT_REQUESTS = 32 FEED_EXPORT_ENCODING = 'utf-8'
boc_forex_spider.py
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
在 MySQL 终端上创建数据库和表
Enter password: ********* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 11 Server version: 8.0.40 MySQL Community Server - GPL ... mysql> CREATE DATABASE IF NOT EXISTS fufubuff; Query OK, 1 row affected (0.00 sec) mysql> USE fufubuff; Database changed mysql> CREATE TABLE IF NOT EXISTS forex_rates ( -> id INT AUTO_INCREMENT PRIMARY KEY, -> currency VARCHAR(255), -> tbp FLOAT, -> cbp FLOAT, -> tsp FLOAT, -> csp FLOAT, -> time VARCHAR(255) -> ); Query OK, 0 rows affected (0.01 sec) mysql> GRANT ALL PRIVILEGES ON fufubuff.* TO 'fufu'@'localhost' IDENTIFIED BY 'hhxxyy120'; FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec) mysql>
运行结果
在mysql上查看
导出结果成为csv
遇到的困难和解决方案
困难一:Scrapy 爬虫未提取任何数据
问题描述:可能是由于网页结构变化导致 XPath 或 CSS 选择器不再准确,或是页面内容通过 JavaScript 动态加载。
解决方案:更新和调整选择器以匹配当前网页的结构。对于动态加载的内容,考虑使用 Scrapy-Splash 或 Selenium 集成来处理 JavaScript。
困难二:数据库连接与权限配置
问题描述:在最初配置 MySQL 数据库时,尝试使用 GRANT ALL PRIVILEGES 语句时出现语法错误,导致无法正确设置用户权限,进而无法将数据插入数据库。
解决方案:仔细检查 MySQL 的权限语法,确保使用正确的 SQL 语句格式。最终使用以下语句成功创建用户并授予权限:
GRANT ALL PRIVILEGES ON fufubuff.* TO 'fufu'@'localhost' IDENTIFIED BY 'hhxxyy120'; FLUSH
困难三:数据格式转换
问题描述:部分汇率数据以字符串形式存在,需要将其转换为浮点数以便在数据库中进行数值分析。
解决方案:在 pipelines.py 中,对抓取到的数据进行类型转换,确保数值字段被正确解析为浮点数。如果数据中存在异常字符或格式,增加数据清洗步骤,过滤或修正不符合预期的数据格式。
心得体会
通过本次作业,我深入理解了 Scrapy 框架中 Item 和 Pipeline 的作用与实现方式。Item 用于定义抓取数据的结构,确保数据的一致性和规范性;Pipeline 则负责对抓取到的数据进行处理、清洗,并最终将其保存到指定的存储介质中,如 MySQL 数据库。本项目中,结合 XPath 精确提取所需数据,并通过 Pipeline 将数据高效地存储到数据库中,极大地提升了数据处理的自动化和可靠性。
在项目实施过程中,我综合运用了 Scrapy 框架、XPath 解析技术和 MySQL 数据库存储,形成了一条完整的数据抓取与存储技术路线。具体步骤如下:
数据抓取:使用 Scrapy 爬虫从中国银行网的外汇汇率页面抓取数据,通过 XPath 精确定位和提取表格中的汇率信息。
数据处理:在 Pipeline 中对抓取到的数据进行必要的清洗和格式转换,确保数据的准确性和一致性。
数据存储:将处理后的数据通过 PyMySQL 库插入到预先创建的 MySQL 数据库表中,实现数据的持久化存储。
本次作业不仅巩固了我对 Scrapy 框架的使用,还加深了我对 XPath 解析技术和 MySQL 数据库操作的理解。在实际项目中,难免会遇到各种问题和挑战,通过查阅资料、调试代码和不断优化,最终成功实现了从中国银行网抓取外汇汇率数据并存储到 MySQL 数据库中的目标。这不仅提升了我的技术能力,也增强了我的问题解决能力和项目实践经验。