数据采集与融合第三次作业——董婕

作业1

要求

指定一个网站,爬取这个网站中的所有图片(中国气象网(http://www.weather.com.cn),使用scrapy框架分别实现 单线程和多线程的方式爬取。

输出信息

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

gitee链接

数据采集与融合实践3第一题

实现过程

观察页面结构

打开开发者模式,观察图片信息,可以发现图片信息均在a标签下的img标签中,提取img标签的src属性值即可。


代码如下。

def extract_image_links(html):
    imgList = re.findall(r'<img.*?src="(.*?)"', html, re.S)
    return imgList

作业要求自动翻页爬取网站图片,但本网站与当当网,财富网不太一样,不是通过改变page或者pn值来进行翻页,是通过点击图片进入下一个页面,随机点了几张图片,但他们的链接都不大一样,没有什么规律。随机截取了几个url。



故不可通过之前的方式来实现自动翻页,继续观察页面,发现图片包含的url均在a标签里的herf属性中。

提取herf属性

next_page_match = re.search('<a href="([^"]*)">', html)

获取herf属性的链接后,可将其赋给url,设定最大爬取页数为11(学号后两位)和最大爬取图片数111(学号后三位),通过while语句循环爬取。

    while page_count <= max_pages and image_count < image_limit:
        # 爬取HTML内容
        html = get_html(url, headers)
        imgList = extract_image_links(html)

        # 多线程下载图片
        remaining_images = min(image_limit - image_count, len(imgList))
        download_images_multi_threaded(imgList[:remaining_images], remaining_images, image_folder)
        image_count += remaining_images

        # 查找下一页的链接
        next_page_match = re.search('<a href="([^"]*)">', html)

        if next_page_match:
            url = next_page_match.group(1)
            page_count += 1
        else:
            break

此外,题目还要求我们用单线程和多线程两种方式,定义单线程函数与多线程函数。

#单线程
def download_image(url, name, folder):
    try:
        resp = requests.get(url)
        with open(os.path.join(folder, name), 'wb') as f:
            f.write(resp.content)
        with print_lock:
            print(f"下载完成: {name} {url}")
    except Exception as e:
        with print_lock:
            print(f"下载失败: {name} {url} -> {e}")
# 函数:多线程下载图片
def download_images_multi_threaded(img_list, limit, folder):
    threads = []
    for i, url in enumerate(img_list[:limit + 1]):
        thread = threading.Thread(target=download_image, args=(url, f'{i}.jpg', folder))
        thread.start()
        threads.append(thread)
    for thread in threads:
        thread.join()

完整代码

import requests
import re
import os
import threading
from datetime import datetime

# 锁用于同步打印消息
print_lock = threading.Lock()

# 函数:获取HTML内容
def get_html(url, headers):
    response = requests.get(url, headers=headers)
    return response.text

# 函数:提取图片链接
def extract_image_links(html):
    imgList = re.findall(r'<img.*?src="(.*?)"', html, re.S)
    return imgList

# 函数:下载单张图片
def download_image(url, name, folder):
    try:
        resp = requests.get(url)
        with open(os.path.join(folder, name), 'wb') as f:
            f.write(resp.content)
        with print_lock:
            print(f"下载完成: {name} {url}")
    except Exception as e:
        with print_lock:
            print(f"下载失败: {name} {url} -> {e}")

# 函数:多线程下载图片
def download_images_multi_threaded(img_list, limit, folder):
    threads = []
    for i, url in enumerate(img_list[:limit + 1]):
        thread = threading.Thread(target=download_image, args=(url, f'{i}.jpg', folder))
        thread.start()
        threads.append(thread)
    for thread in threads:
        thread.join()

# 函数:爬取HTML内容并自动翻页下载
def scrape_and_download_auto_pagination(url, headers, image_limit, max_pages):
    start_time = datetime.now()
    page_count = 1
    image_count = 1

    # 在桌面上创建一个名为`images`的文件夹
    desktop_path = os.path.expanduser("~/Desktop")
    image_folder = os.path.join(desktop_path, "images")
    if not os.path.exists(image_folder):
        os.makedirs(image_folder)

    while page_count <= max_pages and image_count < image_limit:
        # 爬取HTML内容
        html = get_html(url, headers)
        imgList = extract_image_links(html)

        # 多线程下载图片
        remaining_images = min(image_limit - image_count, len(imgList))
        download_images_multi_threaded(imgList[:remaining_images], remaining_images, image_folder)
        image_count += remaining_images

        # 查找下一页的链接
        next_page_match = re.search('<a href="([^"]*)">', html)

        if next_page_match:
            url = next_page_match.group(1)
            page_count += 1
        else:
            break

    end_time = datetime.now()
    print(f"自动翻页爬取和下载完成,总耗时: {end_time - start_time}")

if __name__ == "__main__":
    url = "http://www.weather.com.cn/"  # 你需要替换成目标网站的URL
    headers = {
        'User-Agent': 'Your User Agent',
    }

    image_limit = 111  # 限制下载图片数量
    max_pages = 11  # 最大翻页次数

    print("===== 自动翻页爬取和下载 =====")
    scrape_and_download_auto_pagination(url, headers, image_limit, max_pages)

结果

单线程


可观察到输出有序。

多线程


可观察到输出是被打乱的,不过速度快了很多。

images文件夹部分图片

心得

这题跟之前总体类似,就是新添了一个循环爬取页面的功能,页面结构与之前翻页的页面略有不同,需要自己提取新界面的url循环爬取。

作业2

要求

熟练掌握scrapy中Item、 Pipeline 数据的序列化输出方法; Scrapy+ Xpath+MySQl+数据库存储技术路线爬取股票相关信息。网站:东方财富网(https://www.eastmoney.com

输出信息

gitee链接

数据采集与融合实践3第二题

实现过程

抓包

在search栏里面搜索相关信息,找到对应的包,获取url,并且将f1-18与页面上的表头相对应。

对应信息如下:

            co=data_value['f12'],
            name=data_value['f14'],
            latest=data_value['f2'],
            change=data_value['f4'],
            Randf=data_value['f3'],
            volume=data_value['f5'],
            turnover=data_value['f5'],
            amplitude=data_value['f7'],
            max=data_value['f15'],
            min=data_value['f16'],
            today=data_value['f17'],
            yesterday=data_value['f18']

dj.py代码

import scrapy
import json
from ..items import Dj3Item

class DjSpider(scrapy.Spider):
    name = 'dj'
    # 允许的域名
    allowed_domains = ['65.push2.eastmoney.com']
    # 起始URL
    start_urls = ['http://65.push2.eastmoney.com/api/qt/clist/get?cb=jQuery1124008516432775777205_1697696898159&pn=1&pz=100&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&wbp2u=|0|0|0|web&fid=f3&fs=m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048&fields=f2,f3,f4,f5,f6,f7,f12,f14,f15,f16,f17,f18&_=1697696898163']

    def parse(self, response):
        # 解析JSON数据
        data = self.parse_json(response.text)
        # 从JSON数据中提取需要的信息
        data_values = data['data']['diff']
        items = self.parse_data_values(data_values)
        # 通过生成器方式返回提取的数据
        yield from items

    def parse_json(self, jsonp_response):
        # 从JSONP响应中提取JSON字符串
        json_str = jsonp_response[len("jQuery1124008516432775777205_1697696898159("):len(jsonp_response) - 2]
        return json.loads(json_str)

    def parse_data_values(self, data_values):
        # 遍历数据值列表,创建爬取的数据项
        return [Dj3Item(
            co=data_value['f12'],
            name=data_value['f14'],
            latest=data_value['f2'],
            change=data_value['f4'],
            Randf=data_value['f3'],
            volume=data_value['f5'],
            turnover=data_value['f5'],
            amplitude=data_value['f7'],
            max=data_value['f15'],
            min=data_value['f16'],
            today=data_value['f17'],
            yesterday=data_value['f18']
        ) for data_value in data_values]

items.py

import scrapy

class Dj3Item(scrapy.Item):
    # 定义要爬取的数据字段
    co = scrapy.Field()
    name = scrapy.Field()
    latest = scrapy.Field()
    change = scrapy.Field()
    Randf = scrapy.Field()
    volume = scrapy.Field()
    turnover = scrapy.Field()
    amplitude = scrapy.Field()  
    max = scrapy.Field()
    min = scrapy.Field()
    today = scrapy.Field()
    yesterday = scrapy.Field()
    pass

pipelines.py

import scrapy
import json
import pymysql

class Dj3Pipeline(object):
    conn = None
    cursor = None

    def open_spider(self, spider):
        # 在爬虫启动时执行,用于初始化数据库连接和创建数据表
        self.conn = pymysql.connect(
            host='localhost',
            user='root',
            port=3306,
            password='1',
            database='dong'
        )
        self.cursor = self.conn.cursor()
        with self.conn.cursor() as cursor:
            # 创建数据库和表,如果不存在的话
            cursor.execute('CREATE DATABASE IF NOT EXISTS dj333')
            cursor.execute('USE dj333')
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS dj333 (
                    股票代码 VARCHAR(255),
                    股票名称 VARCHAR(255),
                    最新报价 VARCHAR(255),
                    涨跌幅 VARCHAR(255),
                    涨跌额 VARCHAR(255),
                    成交量 VARCHAR(255),
                    成交额 VARCHAR(255),
                    振幅 VARCHAR(255),
                    最高 VARCHAR(255),
                    最低 VARCHAR(255),
                    今开 VARCHAR(255),
                    昨收 VARCHAR(255)
                )
            """)

    def process_item(self, item, spider):
        # 在爬虫处理每个item时执行,用于将数据插入数据库
        sql = """
            INSERT INTO dj333 (股票代码, 股票名称, 最新报价, 涨跌幅, 涨跌额, 成交量, 成交额, 振幅, 最高, 最低, 今开, 昨收)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        """
        data = (
            item["co"], item["name"], item["latest"], item["change"], item["Randf"],
            item["volume"], item["turnover"], item["amplitude"], item["max"], item["min"],
            item["today"], item["yesterday"]
        )
        with self.conn.cursor() as cursor:
            try:
                cursor.execute(sql, data)  # 执行SQL插入操作
            except Exception as e:
                print(e)
        return item

    def close_spider(self, spider):
        # 在爬虫关闭时执行,用于提交事务和关闭数据库连接
        self.conn.commit()  # 提交事务
        self.conn.close()  # 关闭数据库连接

settings.py

BOT_NAME = 'dj3'

SPIDER_MODULES = ['dj3.spiders']
NEWSPIDER_MODULE = 'dj3.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'

ROBOTSTXT_OBEY = False

结果

命令行运行程序

数据库存储

心得

与之前作业类似,加了个scrapy框架,找包的时候可以先看自己要获取哪些信息,再在search栏里面搜索,并且筛选出特定js包,会更快一些。

作业3

要求

熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取外汇网站数据。

输出信息

gitee链接

数据采集与融合实践3第三题

实现过程

提取信息


可观察到要提取的信息在tr标签下的td标签的文本中,可使用xpath工具帮助定位

由于题目要求输出信息中没有中行折算价,故只提取了TBP、CBP、TSP、BCP、TSP、pub_time六项。提取信息的代码如下。

def parse(self, response):
    # 使用Selenium WebDriver来获取页面内容
    self.driver.get(response.url)
    
    # 创建一个Scrapy Selector对象,用于解析页面内容
    sel = scrapy.Selector(text=self.driver.page_source)
    
    # 选择包含汇率信息的表格
    table = sel.xpath("/html/body/div/div[5]/div[1]/div[2]/table")
    
    # 选择表格中的<tbody标签
    tbody = table.xpath(".//tbody")
    
    # 选择tbody中的所有行(tr标签)
    trs = tbody.xpath(".//tr")
    
    # 遍历每一行,从中提取数据
    for line in trs[1:]:
        # 提取各列数据,使用XPath选择器
        currency = line.xpath("td[1]/text()").extract_first()
        TBP = line.xpath("td[2]/text()").extract_first()
        CBP = line.xpath("td[3]/text()").extract_first()
        TSP = line.xpath("td[4]/text()").extract_first()
        CSP = line.xpath("td[5]/text()").extract_first()
        BCP = line.xpath("td[6]/text()").extract_first()
        pub_time = line.xpath("td[7]/text()").extract_first()
        
        # 创建ExchangeRateItem对象,用于存储提取的数据
        items = ExchangeRateItem()
        items['currency'] = currency
        items['TBP'] = TBP
        items['CBP'] = CBP
        items['TSP'] = TSP
        items['CSP'] = CSP
        items['pub_time'] = pub_time
        
        # 使用yield将ExchangeRateItem对象传递给Scrapy引擎
        yield items

设置settings文件

在settings文件中设置USER_AGENT,ITEM_PIPELINES以及将ROBOTSTXT_OBEY改为False

BOT_NAME = 'boc2'
SPIDER_MODULES = ['boc2.spiders']
NEWSPIDER_MODULE = 'boc2.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'

ROBOTSTXT_OBEY =False

ITEM_PIPELINES = {
    'boc2.pipelines.MyprojectPipeline': 300,
}

定义表

在数据库新建表rates并定义items.py

表rates

items.py

class ExchangeRateItem(scrapy.Item):
    currency = scrapy.Field()
    TBP = scrapy.Field()
    CBP = scrapy.Field()
    TSP = scrapy.Field()
    CSP = scrapy.Field()
    pub_time = scrapy.Field()

定义pipelines

自定义Scrapy管道,编写pipelines.py,将爬取到的数据存储到MySQL数据库中。
定义open_spider函数建立数据库连接

def open_spider(self, spider):
        self.conn = pymysql.connect(
            host='localhost',
            user='root',
            port=3306,
            password='1',
            database='dong'
        )
        self.cursor = self.conn.cursor()

定义process_item函数,将爬取到的数据存储到item对象中,然后通过执行SQL语句,将item对象中的数据插入到数据库中的相应字段中

def process_item(self, item, spider):
        self.cursor.execute("""
            INSERT INTO rates (currency, TBP, CBP, TSP, CSP, pub_time)
            VALUES (%s, %s, %s, %s, %s, %s)
        """, (
            item['currency'],
            item['TBP'],
            item['CBP'],
            item['TSP'],
            item['CSP'],
            item['pub_time']
        ))
        return item

关闭数据库

    def close_spider(self, spider):
        self.conn.commit()
        self.conn.close()

定义run.py

在scrapy项目的目录下新建run.py,可直接在pycharm中运行,便于加断点调试程序。

from scrapy import cmdline
cmdline.execute("scrapy crawl myproject -s LOG_ENABLED=True".split())

结果

命令行运行程序


程序成功运行

数据库存储


成功保存数据

心得

之前scrapy程序能运行,就是一直没结果,弄了好久,后来发现是自己提取元素的代码有问题,学会了使用xpath工具,利用这个可以更好地检验自己提取元素的代码是否正确。

posted @ 2023-10-19 20:39  LLLL2  阅读(34)  评论(0)    收藏  举报