数据采集与融合第三次作业
作业①
要求:
指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。使用scrapy框架分别实现单线程和多线程的方式爬取。
–务必控制总页数(学号尾数2位)、总下载的图片数量(尾数后3位)等限制爬取的措施。
输出信息:
将下载的Url信息在控制台输出,并将下载的图片存储在images子文件中,并给出截图。
爬取图片
🌱实现思路:
爬虫 (weather.py):
WeatherSpider
类通过分页链接递归抓取多个页面,每个页面上的所有图片链接被提取并保存到WeatherSpiderItem
中。爬虫抓取的最大页面数和图片数由settings.py中的配置MAX_PAGES和MAX_IMAGES控制。
Item (items.py):
WeatherSpiderItem类定义爬虫抓取的数据结构
Pipeline (pipelines.py):
该管道负责管理图片的下载过程,包括发起请求、过滤掉下载失败的图片
配置 (settings.py):
爬虫的行为在settings.py中进行配置,例如:控制最大页面数(MAX_PAGES)。控制每个页面抓取的最大图片数(MAX_IMAGES)。配置图片下载的位置和存储格式等。
🎨主要代码:
1.settings.py:
# 控制总页数
MAX_PAGES = 5 # 这里设置为5页,根据学号尾数进行调整
# 控制总下载的图片数量
MAX_IMAGES = 115 # 这里设置为115张
IMAGES_STORE = 'images' # 图片存储目录
ITEM_PIPELINES = {
'weather_spider.pipelines.WeatherSpiderPipeline': 1,
}
2.pipelines.py:
class WeatherSpiderPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for url in item['image_urls']:
yield scrapy.Request(url)
def item_completed(self, results, item, info):
# 过滤没有下载成功的图片
if not item['images']:
raise DropItem("Item contains no images")
return item
3.items.py:
class WeatherSpiderItem(scrapy.Item):
image_urls = scrapy.Field()
images = scrapy.Field()
4.weather.py:
class WeatherSpider(scrapy.Spider):
name = 'weather'
allowed_domains = ['weather.com.cn']
start_urls = ['http://www.weather.com.cn/']
def parse(self, response):
pages = response.css('div.page a::attr(href)').getall()
for page in pages[:self.settings.getint('MAX_PAGES')]:
yield response.follow(page, self.parse_page)
def parse_page(self, response):
image_urls = response.css('img::attr(src)').getall()
item = WeatherSpiderItem()
item['image_urls'] = image_urls[:self.settings.getint('MAX_IMAGES')]
# 输出每个图片的下载URL
for url in item['image_urls']:
self.logger.info(f'Downloading image from URL: {url}')
yield item
运行结果:
终端输出:
images文件夹:
由于网站上图片总数并没有115张,所以只爬取了能爬取到的所有图片。
单线程爬取
scrapy默认多线程,如果需要单线程,在settings.py里进行设置:
# 下载延迟
DOWNLOAD_DELAY = 1 # 单线程下载速度
# 单线程模式
CONCURRENT_REQUESTS = 1
爬取结果与多线程一样,不再展示。
心得体会
- 这次开发Scrapy爬虫项目,我对数据抓取、处理和存储有了更加深入的理解,尤其是Scrapy框架的高效性和灵活性。
- Scrapy框架为我们提供了清晰的结构,爬虫的各个部分(爬虫、Item、Pipeline等)相互配合,使得开发过程变得系统化。特别是在处理大规模数据抓取时,Scrapy能够自动化地管理请求、响应和数据存储,大大提高了效率。
作业②
要求:
熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取股票相关信息。
候选网站:
东方财富网:https://www.eastmoney.com/
输出信息:
MySQL数据库存储和输出格式如下:
表头英文命名例如:序号id,股票代码:bStockNo……,由同学们自行定义设计
股票代码 | 股票名称 | 最新报价 | 涨跌幅 | 涨跌额 | 成交量 | 成交额 | 振幅 | 最高 | 最低 | 今开 | 昨收 |
---|---|---|---|---|---|---|---|---|---|---|---|
301522 | N上大 | 79.85 | 1060.61 | 72.97 | 595675 | 5031370784.78 | 582.56 | 102.00 | 61.92 | 61.92 | 6.88 |
爬取股票信息
🌱解析网页结构:
1.选择爬取“沪深京A股”板块
可以得到url为:https://quote.eastmoney.com/center/gridlist.html#hs_a_board
2.股票信息结构
根据此,设计XPATH爬取相应所需信息
3.翻页处理
根据此,设计翻页逻辑
🌱实现思路:
1. Selenium 模拟浏览器爬取
- 东方财富网的股票数据是通过 JavaScript 动态加载的,Scrapy 无法直接抓取。
- 所以,使用 Selenium 来模拟浏览器,加载完整的页面内容,再用 Selenium 定位和提取动态加载的数据。这样可以确保数据在页面上完全加载后再进行解析。
2. MySQL 数据库准备
- 在 MySQL 中创建数据库和表,数据库 stock_db,表 stock_data 。
- 如果数据库中尚未存在
stock_data
表,创建该表以存储股票相关数据
3. 爬虫编写
初始化 Selenium
- 在爬虫类的
__init__
方法中,初始化 Selenium WebDriver,配置 ChromeOptions 为无头模式(headless
)并设置 User-Agent。
解析网页
- 在
parse
方法中,使用 Selenium 加载东方财富网的目标 URL(例如:https://quote.eastmoney.com/center/gridlist.html#hs_a_board
)。 - 等待页面加载完成后,定位包含股票信息的表格。
- 使用显式等待(如
WebDriverWait
和expected_conditions
)确保数据完全加载,避免出现元素未加载导致定位失败的情况。
数据提取
- 通过 Selenium 定位包含股票信息的行,逐行提取股票的各项数据,包括代码、名称、最新报价、涨跌额等。
- 使用 Selenium 的
find_elements_by_xpath
方法来获取每一行的单元格数据,确保定位路径正确。 - 将提取的数据存储到
StockItem
对象中,便于后续在 pipeline 中处理。
4. 数据存储
连接数据库
- 在
open_spider
方法中,建立与 MySQL 的连接。 - 在
close_spider
方法中,关闭数据库连接。
数据插入
- 在
process_item
方法中,将每条爬取到的数据插入到数据库表stocks
中。 - 检查字段格式,例如确保日期和浮点数的格式符合 MySQL 的要求。
- 使用
try-except
处理插入时可能出现的异常,例如重复插入、格式不符等。
🎨主要代码:
- 初始化爬虫并加载页面,使用 Selenium 的
driver.get()
方法加载东方财富网的目标页面。设置max_pages
为最大页面数(5页),并初始化current_page
为 1,表示从第一页开始爬取数据。 - 使用显式等待(WebDriverWait)确保页面加载完成,并定位包含股票信息的表格行( 元素)。一旦找到该表格,将行数据存入 rows 列表。
def parse(self, response):
self.driver.get(response.url)
max_pages = 5
current_page = 1
while current_page <= max_pages:
# 等待页面加载必要的元素
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//table[@id="table_wrapper-table"]/tbody/tr'))
)
rows = self.driver.find_elements(By.XPATH, '//table[@id="table_wrapper-table"]/tbody/tr')
print(f"Processing page {current_page} with {len(rows)} rows")
for row in rows:
cells = row.find_elements(By.TAG_NAME, 'td')
- 翻页操作,尝试点击“下一页”按钮。使用显式等待确保“下一页”按钮可点击,然后点击按钮并递增 current_page。等待页面刷新,以确保新数据加载完成。在页面刷新期间,使用 EC.staleness_of(rows[0]) 等待上一页的元素失效。如果无法找到“下一页”按钮,或点击出现异常,则终止循环。
# 检查是否有下一页并且尝试点击
try:
next_button = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable(
(By.XPATH, '//a[@class="next paginate_button"]'))
)
next_button.click()
current_page += 1
# 等待页面刷新
WebDriverWait(self.driver, 10).until(
EC.staleness_of(rows[0])
)
except Exception as e:
print(f"No more pages or unable to click next. Error: {e}")
break
- Scrapy 数据管道,用于将爬取的股票数据存储到 MySQL 数据库中
class StockPipeline:
def open_spider(self, spider):
# 连接 MySQL 数据库
self.conn = mysql.connector.connect(
host=spider.settings.get('MYSQL_HOST'),
user=spider.settings.get('MYSQL_USER'),
password=spider.settings.get('MYSQL_PASSWORD'),
database=spider.settings.get('MYSQL_DATABASE')
)
self.cursor = self.conn.cursor()
StockItem
类用于存储爬取到的股票数据,并定义了与股票相关的多个字段。每个字段使用scrapy.Field()
进行声明,以便 Scrapy 能够识别并处理这些字段。
class StockItem(scrapy.Item):
id = scrapy.Field()
bStockNo = scrapy.Field() # 股票代码
bStockName = scrapy.Field() # 股票名称
bLastPrice = scrapy.Field() # 最新报价
bChangeRate = scrapy.Field() # 涨跌幅
bChangeAmount = scrapy.Field() # 涨跌额
bVolume = scrapy.Field() # 成交量
bAmplitude = scrapy.Field() # 振幅
bHigh = scrapy.Field() # 最高价
bLow = scrapy.Field() # 最低价
bOpen = scrapy.Field() # 今开
bPrevClose = scrapy.Field() # 昨收
运行结果:
爬取到了所需股票信息。
心得体会
在这次项目中,我学习并掌握了如何在 Scrapy 项目中集成 Selenium 和 MySQL 数据库。
- 动态页面处理的挑战
在现代网页开发中,许多内容是通过 JavaScript 动态加载的,传统的 Scrapy 抓取方式无法直接获取这些数据。通过将 Selenium 集成到 Scrapy 中,可以加载并解析动态内容,从而实现对更多复杂页面的抓取。这让我认识到,在数据爬取中灵活运用多种工具是非常重要的。
- 数据存储的重要性
在实际项目中,数据的持久化存储是非常关键的一步。通过将数据存储在 MySQL 数据库中,可以方便地对抓取的数据进行查询和分析。建立合理的数据库结构和配置合适的 Item Pipeline,不仅提高了数据处理的效率,也使得数据的管理更加规范化。
- 自动化数据抓取的应用前景
在处理金融数据等实时数据时,自动化数据抓取可以极大提高数据的时效性和更新频率。这种技能在市场分析、商业情报、风险管理等领域都有广泛的应用前景。通过本次实践,我对自动化数据抓取的价值有了更深的体会,并对未来相关应用充满了期待。
遇到的问题与解决思路
在项目过程中,遇到了一些常见问题:
- Selenium 配置问题:需要正确配置 WebDriver 和设置等待条件,确保页面加载完成后再提取数据。
- MySQL 存储问题:正确设计数据库结构,避免数据类型不匹配或字段空值导致的错误。
- 数据提取结构复杂:通过浏览器的开发者工具确定表格的确切结构,利用 Selenium 的 Xpath 选择器准确定位需要的字段。为了简化代码和提升稳定性,可以将提取的数据字段逐个检查和验证,确保每一列的数据提取无误。
作业③
要求:
熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取外汇网站数据。
候选网站:
中国银行网:https://www.boc.cn/sourcedb/whpj/
输出信息:
Currency | TBP | CBP | TSP | CSP | Time |
---|---|---|---|---|---|
阿联酋迪拉姆 | 198.58 | 192.31 | 199.98 | 206.59 | 11:27:14 |
爬取中国银行外汇数据
🌱解析网页结构:
外汇信息
根据此,设计XPATH爬取信息方式。
🌱实现思路:
由于页面内容是动态加载的,我们采用了 Selenium 驱动的 WebDriver 来加载网页,以确保页面完全呈现后再抓取数据:
1.初始化 Selenium WebDriver
在爬虫的初始化部分配置 Selenium 的 Chrome WebDriver,这样在实际爬取过程中可以自动加载动态内容。
2. 数据抓取流程
- 通过 Selenium 加载目标网页,并设置明确的等待条件,以确保页面内容加载完成,尤其是表格内容。
- 使用 XPath 定位页面中的表格,并对表格的每一行进行数据提取。
- 每行数据分别对应
ForexItem
中的一个字段,并将每个ForexItem
实例传递到数据存储管道进行处理。
3. 数据存储管道
在 pipelines.py
文件中,通过 MySQL 数据库管道将数据持久化存储:
-
数据库连接管理:在
open_spider
方法中建立数据库连接,并在初次启动时检查并创建数据表。这一步可以确保数据存储有条不紊地进行,且不会重复创建表格。 -
数据插入与格式化:在
process_item
中,将每一条数据记录插入到 MySQL 数据表中,并根据特定格式处理数值类型(如价格、变化幅度等)。 -
关闭数据库连接:在爬虫结束时关闭数据库连接,确保没有资源泄露。
4. 配置文件设置
在 settings.py
中,设置了关键的全局配置,包括启用 Selenium 中间件、MySQL 数据库连接参数等。Scrapy 项目配置的模块化使得在不同环境中更改配置变得简单,同时确保项目的可移植性。
🎨主要代码:
parse
方法是 Scrapy 中用于处理响应数据的主要方法。这里使用显式等待 (WebDriverWait) 来确保页面上需要的数据元素(外汇牌价表格)已经加载完成。我们等待的条件是页面中的表格元素(通过 XPath 定位)能够被找到。成功加载页面后,我们使用 find_elements 获取所有包含数据的行,每一行表示一个货币的外汇信息。
def parse(self, response):
# 使用 Selenium 加载页面
self.driver.get(response.url)
try:
# 显式等待,确保表格第一个单元格加载完成
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located(
(By.XPATH, '//table[@cellpadding="0" and @cellspacing="0" and @width="100%"]/tbody/tr/td'))
)
print("Table loaded successfully.")
except Exception as e:
print("Error loading table:", e)
self.driver.quit()
return
# 定位表格行并提取数据
rows = self.driver.find_elements(By.XPATH,
'//table[@cellpadding="0" and @cellspacing="0" and @width="100%"]/tbody/tr')
print(f"Found {len(rows)} rows")
for row in rows:
cells = row.find_elements(By.TAG_NAME, 'td')
ForexPipeline
类用于处理从 Scrapy 爬虫获取的数据并将其存储到 MySQL 数据库中。该类定义了打开数据库连接、创建表格、插入数据,以及关闭数据库连接的过程。(只展示部分)
class ForexPipeline:
def open_spider(self, spider):
self.conn = mysql.connector.connect(
host='localhost',
user='root',
password='123456',
database='forex_data'
)
self.cursor = self.conn.cursor()
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS forex_data (
id INT AUTO_INCREMENT PRIMARY KEY,
Currency VARCHAR(255),
TBP FLOAT,
CBP FLOAT,
TSP FLOAT,
CSP FLOAT,
Time TIME
)
''')
self.conn.commit()
ForexItem
类定义了数据结构,用于存储和管理从 Scrapy 爬虫中提取的外汇信息。
class ForexItem(scrapy.Item):
Currency = scrapy.Field()
TBP = scrapy.Field()
CBP = scrapy.Field()
TSP = scrapy.Field()
CSP = scrapy.Field()
Time = scrapy.Field()
运行结果:
心得体会
- 对scrapy框架更加熟悉,非常清晰高效。
- 对Selenium模拟浏览器进行自动抓取、自动翻页等的操作理解更加深入
遇到的问题与解决思路
数据格式处理
- 问题:一些数据带有符号(如百分号“%”)或其他特殊字符,直接插入 MySQL 时会出错。
- 解决方法:在 process_item 中对数据进行清洗。例如,去除多余字符,将百分比转换为浮点数,并对空数据处理成 None,确保数据格式与数据库表结构匹配。
MySQL 数据库连接异常
- 问题:在插入数据时出现了 MySQL 连接问题,比如“Access denied for user”或“数据库管道连接失败”。
- 解决方法:确保数据库配置正确,包括用户名、密码和数据库名称。同时,确认 MySQL 服务已启动并允许远程访问。加入异常处理,避免程序因数据库错误而中断。
数据重复与插入错误
- 问题:有时会出现数据重复或因为主键冲突导致的插入错误。
- 解决方法:在数据库中增加数据唯一约束,或者在抓取过程中判断数据是否已经存在,避免重复插入。对于可能的插入错误,加入异常处理逻辑,确保数据管道稳定。