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

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

作业①:

要求:指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。实现单线程和多线程的方式爬取。
–务必控制总页数(学号尾数2位)、总下载的图片数量(尾数后3位)等限制爬取的措施。

输出信息: 将下载的Url信息在控制台输出,并将下载的图片存储在images子文件中,并给出截图。


爬取的网页:https://sc.chinaz.com/tupian/
使用Scrapy需要使用命令创建爬虫,scrapy startproject命令创建爬虫项目
image

scrapy genspider <爬虫名称> <域名>创建爬虫程序
image

单线程爬取动物图片

关于实现多线程和单线程,Scrapy本身并不直接支持多线程,因为它是基于异步IO的。但是可以通过改变settings中CONCURRENT_REQUESTS的值实现类似多线程的效果,设置并发请求数,为1近似单线程,也可以设置成其他值近似多线程(默认16)
step1:设置为单线程方式爬取,settings.py中注设置最大并发请求数量CONCURRENT_REQUESTS=1
image
step2:找到图片对应的网站,发送请求

点击查看代码
 # 方法:生成初始请求并发送到目标网址
    def start_requests(self):
        # 从50页循环遍历目标网站
        for page in range(50, 60):
            # 如果已经达到目标数量,停止生成新的请求
            if self.图片计数 >= self.最大图片数:
                break
            url = f"https://sc.chinaz.com/tupian/dongwutupian_{page}.html"

            # 使用 scrapy.Request 发送请求,获取页面内容
            # 该请求的响应将由 'parse' 方法处理
            yield scrapy.Request(url=url, callback=self.parse)
step3:对请求得到的网页进行解析

先定义一个图片数目计数器

点击查看代码
 def __init__(self, *args, **kwargs):
        super(ImageSpider, self).__init__(*args, **kwargs)
        self.图片计数 = 0  # 初始化图片计数器
        self.最大图片数 = 150  # 设置最大下载数量

再进行解析

点击查看代码
def parse(self, response):
        # 如果已经达到目标数量,直接返回
        if self.图片计数 >= self.最大图片数:
            return
        
        图片列表 = response.xpath('/html/body/div[3]/div[2]/div')
        for i in 图片列表:
            # 如果已经达到目标数量,停止处理
            if self.图片计数 >= self.最大图片数:
                break
            
            图片地址 = 'https:' + i.xpath('./img/@data-original').extract_first()
            if 图片地址 and 图片地址 != 'https:None':  # 检查地址是否有效
                print(f"图片 {self.图片计数 + 1}/150: {图片地址}")
                item对象 = TupianItem()    
                item对象['链接地址'] = 图片地址    
                self.图片计数 += 1  # 增加计数器
                yield item对象
step4:构建items对象
点击查看代码
class TupianItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    链接地址 = scrapy.Field()

step5:新建管道类,并导入ImagesPipeline
点击查看代码
from itemadapter import ItemAdapter


import scrapy
from scrapy.pipelines.images import ImagesPipeline
class 图片管道类(ImagesPipeline):    
    def get_media_requests(self, item, info):        
        图片地址=item['链接地址']        
        yield scrapy.Request(图片地址)    
    def file_path(self, request, response=None, info=None, *, item=None):        
        图片名称 =request.url.split('/')[-1]        
        return 图片名称    
    def item_completed(self, results, item, info):        
        return item
step6:配置settings.py
点击查看代码
ITEM_PIPELINES = {   "tupian.pipelines.图片管道类": 300,}
#配置请求头
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
#图片存放地
IMAGES_STORE='images'
step7:运行run.py或运行下列代码

scrapy crawl image -s LOG_ENABLED=false

运行结果:

image

image

多线程爬取人物图片

step1:修改多线程
image
step2:修改url,将上述代码中url改为人物图片其余不变
url = f"https://sc.chinaz.com/tupian/renwutupian_{page}.html"

运行结果:

image

image

心得体会

为了实现题目中的限制要求,我从第50页开始爬取,一共爬取150张图片满足了学号的限制,网页获取也找到规律发现网页基本上是最后一个数字变化来代表每一页(下图的50例如),设置一个循环就能翻页

image

首先,在单线程方式下,设置了 CONCURRENT_REQUESTS=1,保证每次只发送一个请求,逐一处理每个页面的图像。这种方式的优势在于操作简单、易于调试,可以清晰地控制每一步的执行过程。通过 scrapy.Request 发送请求并解析网页,获取图片 URL 后,再逐个请求下载图片
图片名称 =request.url.split('/')[-1]根据控制台所见设计切割只保留最后面
image
然而,单线程的效率较低,特别是当网页图片较多时,下载速度会变得较慢。为了解决这个问题,我尝试了多线程方式。在 settings.py 中将 CONCURRENT_REQUESTS 设置为 16,这样 Scrapy 就能同时处理多个请求,大大提高了抓取和下载的效率。多线程方式虽然能加速爬取过程,但也增加了资源占用,可能会导致服务器负载过高,需要合理设置并发数。


作业②

动态爬取股票相关信息实验
要求:熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取股票相关信息。

候选网站:东方财富网:https://www.eastmoney.com/

输出信息:MySQL数据库存储和输出格式如下:

image

前情摘要:
直接解析网页时候输出data发现没有我们想要的信息o.O,后面发现这个网站是js动态渲染的,然后就上网搜教程怎么解决动态渲染,然后找到了下列教程:https://blog.csdn.net/FuncIsle/article/details/153402856
然后才完成了这次作业

代码部分

step1:
由于网站的数据是实时加载的,但是Scrapy是静态的,不能获取JavaScript执行过的html,所以Selenium相关的处理程序配置写在中间件中,因为downloader会得到spider请求响应的html,需要在中间件中使用Selenium重新处理html使得是JavaScript执行过的,并封装成新的响应发送给spider解析

点击查看代码
class SeleniumMiddleware(object):
  def process_request(self,request,spider):
      url = request.url
      browser = webdriver.Chrome()
      browser.get(url)
      
      # 增加等待时间,让JavaScript完全加载
      time.sleep(10)
      
      # 尝试等待表格数据加载
      try:
          from selenium.webdriver.common.by import By
          from selenium.webdriver.support.ui import WebDriverWait
          from selenium.webdriver.support import expected_conditions as EC
          
          # 等待表格或数据容器出现
          WebDriverWait(browser, 20).until(
              EC.presence_of_element_located((By.TAG_NAME, "tbody"))
          )
          print("表格数据已加载")
      except Exception as e:
          print(f"等待表格加载超时: {e}")
      
      html = browser.page_source
      browser.close()
      return HtmlResponse(url=url, body=html, request=request, encoding="utf-8", status=200)
step2:在settings.py中配置这个中间件也顺手启动一下后面用到的数据库pipline
点击查看代码
SELENIUM_ENABLED = True
DOWNLOADER_MIDDLEWARES = {
  "work2.middlewares.SeleniumMiddleware": 543,
}
step3:解析响应部分,因为获得的是JavaScript处理过的html所以可以直接使用Xpath获取所需文本,使用position定位需要字段对应的标签下的文本,再使用extract_first提取出文本值
点击查看代码
	try:
          dammit=UnicodeDammit(response.body,["utf-8","gbk"])
          data=dammit.unicode_markup
          selector=Selector(text=data)
          
          # 查找股票数据行
          tbody_rows = selector.xpath("//tbody/tr")
          multi_td_rows = selector.xpath("//tr[count(td) > 5]")
          
          # 使用最可能包含数据的选择器
          if len(tbody_rows) > 0:
              items = tbody_rows
          elif len(multi_td_rows) > 0:
              items = multi_td_rows
          else:
              items = selector.xpath("//tr[td]")
          
          print(f"成功找到 {len(items)} 条股票数据")
          
          for i in items:
              no = i.xpath(".//td[position()=1]//text()")
              name = i.xpath(".//td[position()=3]//text()")
              code = i.xpath(".//td[position()=2]//text()")
              zxj = i.xpath(".//td[position()=5]//text()")
              zdf = i.xpath(".//td[position()=6]//text()")
              zde = i.xpath(".//td[position()=7]//text()")
              cjl = i.xpath(".//td[position()=8]//text()")
              zf = i.xpath(".//td[position()=10]//text()")
              zg = i.xpath(".//td[position()=11]//text()")
              zd = i.xpath(".//td[position()=12]//text()")
              jk = i.xpath(".//td[position()=13]//text()")
              zs = i.xpath(".//td[position()=14]//text()")
              item=Work2Item()
              item["no"]=no.extract_first() or ""
              item["code"]=code.extract_first() or ""
              item["name"]=name.extract_first() or ""
              item["zxj"]=zxj.extract_first() or ""
              item["zdf"]=zdf.extract_first() or ""
              item["zde"]=zde.extract_first() or ""
              item["cjl"]=cjl.extract_first() or ""
              item["zf"]=zf.extract_first() or ""
              item["zg"]=zg.extract_first() or ""
              item["zd"]=zd.extract_first() or ""
              item["jk"]=jk.extract_first() or ""
              item["zs"]=zs.extract_first() or ""
              yield item
      except Exception as e:
          self.logger.error(f"解析股票数据时出错: {e}")
          print(f"Error parsing stock data: {e}")
step4:pipline.py文件书写(这里使用sqlite)

open_spider在开启爬虫时执行,连接数据库

点击查看代码
def open_spider(self, spider):
        print("SQLite数据库Pipeline启动")
        try:
            # 创建SQLite数据库文件
            self.db_path = "stocks.db"
            self.con = sqlite3.connect(self.db_path)
            self.cursor = self.con.cursor()
            
            # 创建stocks表
            self.cursor.execute("""
                CREATE TABLE IF NOT EXISTS stocks (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    no TEXT,
                    code TEXT,
                    name TEXT,
                    zxj TEXT,
                    zdf TEXT,
                    zde TEXT,
                    cjl TEXT,
                    zf TEXT,
                    zg TEXT,
                    zd TEXT,
                    jk TEXT,
                    zs TEXT,
                    created_time DATETIME DEFAULT CURRENT_TIMESTAMP
                )
            """)
            
            # 清空表
            self.cursor.execute("DELETE FROM stocks")
            self.con.commit()
            self.opened = True
            
            # 打印表头
            print("{:<5}{:<10}{:<10}{:<8}{:<8}{:<10}{:<10}{:<8}{:<10}{:<8}{:<8}{:<8}".format(
                "序号", "代码", "名称", "最新价", "涨跌幅", "涨跌额", "成交量", "振幅", "最高", "最低", "今开", "昨收"))
            print(f"✓ SQLite数据库已创建: {os.path.abspath(self.db_path)}")
            
        except Exception as e:
            print(f"SQLite数据库连接失败: {e}")
            self.opened = False
close_spider在爬虫结束时执行,关闭数据库
点击查看代码
def close_spider(self, spider):
        if self.opened:
            self.con.commit()
            self.con.close()
            self.opened = False
            print(f"✓ 数据已保存到SQLite数据库: {os.path.abspath(self.db_path)}")
        print("SQLite数据库Pipeline关闭")
process_item在每次接收到spider解析后的数据执行,先打印出数据再将数据插入到stocks表中
点击查看代码
 def process_item(self, item, spider):
        try:
            # 格式化并打印每个项的内容
            print("{:<5}{:<13}{:<10}{:<10}{:<10}{:<10}{:<10}{:<15}{:<10}{:<10}{:<10}{:<10}".format(
                item["no"], item["code"], item["name"], item["zxj"], item["zdf"], item["zde"], item["cjl"], item["zf"],
                item["zg"], item["zd"], item["jk"], item["zs"]))

            if self.opened:
                # 插入数据到SQLite数据库
                self.cursor.execute("""
                    INSERT INTO stocks (no, code, name, zxj, zdf, zde, cjl, zf, zg, zd, jk, zs)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    item["no"], item["code"], item["name"], item["zxj"], item["zdf"], item["zde"],
                    item["cjl"], item["zf"], item["zg"], item["zd"], item["jk"], item["zs"]
                ))
                
        except Exception as e:
            print(f"SQLite数据插入错误: {e}")
        
        return item
step5:items.py书写
点击查看代码
  no = scrapy.Field()
  code= scrapy.Field()
  name = scrapy.Field()
  zxj = scrapy.Field()
  zdf = scrapy.Field()
  zde = scrapy.Field()
  cjl = scrapy.Field()
  zf = scrapy.Field()
  zg = scrapy.Field()
  zd = scrapy.Field()
  jk = scrapy.Field()
  zs = scrapy.Field()

运行结果

我的代码根据网上学习首先跳转一个浏览器,关闭广告后它会自动爬取网页,同时可以实现翻页功能
image

image
数据库如图所示
image

心得体会

找到需要爬取的股票html,看到股票信息都在表格中,每个股票在一个元素中,字段分别在不同的中
image

已熟练掌握 Selenium 与 Scrapy 框架的协同爬取方案,针对 JavaScript 动态渲染页面的爬取场景,构建了“动态渲染-内容抓取”的高效工作流:通过预先调用 Selenium 模拟浏览器内核加载页面,完成 JavaScript 脚本执行、DOM 节点动态生成及数据渲染,确保 Scrapy 接收响应时可获取完整的目标 HTML 结构,彻底解决了传统爬虫因无法解析动态内容导致的数据缺失问题。
同时,深入掌握 Scrapy 与 MySQL 数据库的交互机制,实现了爬取数据的规范化存储。通过自定义数据模型、配置数据库连接池、优化 SQL 语句执行逻辑,建立了从数据抓取、清洗、验证到持久化存储的全流程闭环,确保数据存储的安全性、一致性与可扩展性,进一步夯实了基于 Scrapy 框架的复杂数据爬取与存储能力。。


作业③:

要求:熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取外汇网站数据。
候选网站:中国银行网:https://www.boc.cn/sourcedb/whpj/
输出信息:
image

代码部分

step1:找到对应的网站页面,对不同页面的url进行处理

点击查看代码
def start_requests(self):
        pageNum = 2  # 设置要爬取的页数

        # 循环遍历页面,构造每个页面的 URL
        for page in range(0, pageNum):
            if page == 0:
                # 第一页的 URL 结构不同,需要单独处理
                url = "https://www.boc.cn/sourcedb/whpj/index.html"
            else:
                # 其他页面的 URL 结构
                url = f"https://www.boc.cn/sourcedb/whpj/index_{page}.html"

            # 使用 scrapy.Request 发送请求,并指定回调函数 parse 来处理响应
            yield scrapy.Request(url=url, callback=self.parse)
step2:解析页面并提取数据
点击查看代码
def parse(self, response):
        data = response.body.decode('utf-8')  # 将响应的字节数据解码为 UTF-8 字符串

        # 使用 Scrapy 的 Selector 类解析解码后的 HTML 数据
        selector = scrapy.Selector(text=data)

        # 定位包含外汇牌价数据的表格,使用 XPath 表达式查找
        body = selector.xpath("//div[@class='publish']//table")

        # 提取表格中的所有行(<tr> 元素)
        trs = body.xpath(".//tr")

        # 提取表头信息(第二行),并打印表头内容用于调试
        info_th = trs[1].xpath('.//th/text()').extract()
        print(info_th)

        # 遍历所有行,从第三行开始(忽略表头),提取每行的数据
        for info in trs[2:]:
            item = BankItem()  # 创建一个 BankItem 实例,用于存储提取的数据

            # 使用 XPath 表达式提取每列的数据,并存储到 item 对应的字段中
            item["Currency"] = info.xpath("./td[1]/text()").get()  # 提取货币名称
            item["TBP"] = info.xpath("./td[2]/text()").get()  # 提取现汇买入价
            item["CBP"] = info.xpath("./td[3]/text()").get()  # 提取现钞买入价
            item["TSP"] = info.xpath("./td[4]/text()").get()  # 提取现汇卖出价
            item["CSP"] = info.xpath("./td[5]/text()").get()  # 提取现钞卖出价
            item["Time"] = info.xpath("./td[7]/text()").get()  # 提取更新时间

            # 将提取的数据传递到 item 管道(pipelines)进行处理
            yield item
step3:定义一个 BankItem 类,用于存储从网页中提取的数据
点击查看代码
    Currency = scrapy.Field()

    # 定义用于存储“现汇买入价”的字段
    TBP = scrapy.Field()

    # 定义用于存储“现钞买入价”的字段
    CBP = scrapy.Field()

    # 定义用于存储“现汇卖出价”的字段
    TSP = scrapy.Field()

    # 定义用于存储“现钞卖出价”的字段
    CSP = scrapy.Field()

    # 定义用于存储“更新时间”的字段
    Time = scrapy.Field()
step5:mysql中创建数据库

image
step6:在mysql数据库创建bank表,并将爬取到的数据存储到表中

点击查看代码
class ChinaBankPipeline:
    count = 0  # 定义一个类变量 count,用于跟踪处理的数据项数

    # 处理每个传入的 item 对象
    def process_item(self, item, spider):
        try:
            # 连接到 MySQL 数据库(请根据实际情况修改为你的数据库配置)
            con = mysql.connector.connect(
                host="localhost",  # 数据库主机地址
                user="root",  # 数据库用户名
                password="这里换成你的密码",  # 数据库密码
                database="DataAcquisition"  # 目标数据库名称
            )
            cursor = con.cursor()  # 创建一个游标对象,用于执行 SQL 语句

            # 如果这是第一个被处理的 item,则创建表结构
            if ChinaBankPipeline.count == 0:
                try:
                    # 删除旧表(如果已存在)以避免冲突
                    cursor.execute("DROP TABLE IF EXISTS bank")

                    # 创建新的表结构
                    sql = """
                    CREATE TABLE IF NOT EXISTS bank (
                        Currency VARCHAR(64) PRIMARY KEY,
                        TBP FLOAT,
                        CBP FLOAT,
                        TSP FLOAT,
                        CSP FLOAT,
                        Time VARCHAR(64)
                    )
                    """
                    cursor.execute(sql)  # 执行 SQL 语句创建表
                except Exception as e:
                    print("Error creating table:", e)  # 如果出错,打印错误信息

            # 准备插入或更新数据的 SQL 语句(处理重复主键)
            sql = """
            INSERT INTO bank (Currency, TBP, CBP, TSP, CSP, Time)
            VALUES (%s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                TBP = VALUES(TBP),
                CBP = VALUES(CBP),
                TSP = VALUES(TSP),
                CSP = VALUES(CSP),
                Time = VALUES(Time)
            """
            try:
                # 从 item 对象中提取数据
                currency = item.get('Currency')  # 获取货币信息
                tbp = item.get('TBP')  # 获取现汇买入价
                cbp = item.get('CBP')  # 获取现钞买入价
                tsp = item.get('TSP')  # 获取现汇卖出价
                csp = item.get('CSP')  # 获取现钞卖出价
                time = item.get('Time')  # 获取更新时间

                # 执行 SQL 插入/更新语句,将数据插入或更新数据库
                cursor.execute(sql, (currency, tbp, cbp, tsp, csp, time))
                print(f"✓ 处理货币数据: {currency}")
            except Exception as err:
                print(f"Error processing data for {currency}: {err}")  # 如果处理数据时出错,打印错误信息

            # 提交事务,保存数据更改
            con.commit()
            # 关闭数据库连接
            con.close()

        except Exception as err:
            print("Error:", err)  # 如果连接数据库或其他操作失败,打印错误信息

        # 增加计数器,表示已处理的数据项数
        ChinaBankPipeline.count += 1

        # 返回处理后的 item 对象
        return item

step7:scrapy crawl bankspider -L INFO运行项目

运行结果

image

step8:mysql中查看结果

image

心得体会

分析网页发现数据都在表格中,在<table>下的<tbody>中
每一行数据在<tr>中,每个字段在不同的<td>中,其实后面spider获取的响应没有<tbody>元素
image
parse 方法解析网页响应并提取外汇牌价数据。首先将响应字节数据解码为 UTF-8 字符串,然后用 Scrapy 的 Selector 类解析 HTML 内容,定位到包含数据的表格。提取表格行,跳过表头,从第三行开始逐行遍历。
对于每行,创建 BankItem 实例,使用 XPath 提取各列的数据:包括货币名称、现汇买入价、现钞买入价、现汇卖出价、现钞卖出价和更新时间,将其存入 item 对象。最终,通过 yield 将 item 传递到管道进行进一步处理。
观察到原界面有空值(如下),xpath如果直接使用.extract(),就没有办法获取空值,故采用get方法
image


码云:https://gitee.com/Buonanotte528/2025Data-Collection/tree/master/数据采集与融合技术第三次作业

posted @ 2025-11-17 21:01  Buonanotte528  阅读(2)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end