数据采集实践第一次作业

作业①

1)、软科中国大学排名定向爬虫实验

页面结构分析

打开软科2020年中国大学排名页面( http://www.shanghairanking.cn/rankings/bcur/2020 ),使用浏览器开发者工具(F12)审查页面结构。可以发现:学校信息包含在 <tbody> 标签中,每一条学校信息包含在 <tr> 标签中;学校名称包含在 class='univname' 的 div 下的 class='name-cn' 的 span 标签中;排名、省市、学校类型和总分信息在 <td> 标签中,其中排名在 td[0],学校名称在 td[1] 的嵌套span中,省市在 td[2],学校类型在 td[3],总分在 td[4]。

屏幕截图 2026-10-09 141310

完整代码

"""
作业①:使用requests和BeautifulSoup爬取软科2020中国大学排名
"""

import requests
from bs4 import BeautifulSoup


def get_html_text(url):
    """
    向目标URL发送HTTP请求,获取网页HTML文本
    :param url: 目标网址
    :return: 网页HTML文本字符串,请求失败返回空字符串
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/120.0.0.0 Safari/537.36'
    }
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()          # 检查请求是否成功
        response.encoding = 'utf-8'
        return response.text
    except requests.RequestException as e:
        print(f'请求失败:{e}')
        return ''


def parse_university_list(html):
    """
    解析HTML文本,提取大学排名数据
    :param html: 网页HTML文本
    :return: 包含每所大学信息的列表,每个元素为[排名, 学校名称, 省市, 学校类型, 总分]
    """
    soup = BeautifulSoup(html, 'lxml')
    table = soup.find('tbody')               # 定位排名表格主体
    if not table:
        print('未找到排名表格,请检查网页结构是否变更')
        return []

    rows = table.find_all('tr')              # 获取所有行
    univ_list = []

    for row in rows:
        # 提取学校名称
        school_tag = row.select("div[class='univname'] span[class='name-cn']")
        if not school_tag:
            continue
        school = school_tag[0].text.strip()

        # 提取其余字段
        tds = row.find_all('td')
        if len(tds) < 5:
            continue

        rank = tds[0].text.strip()           # 排名
        province = tds[2].text.strip()       # 省市
        univ_type = tds[3].text.strip()      # 学校类型
        score = tds[4].text.strip()          # 总分

        univ_list.append([rank, school, province, univ_type, score])

    return univ_list


def print_university_list(univ_list):
    """
    格式化打印大学排名信息
    :param univ_list: 大学排名数据列表
    """
    # 打印表头
    print(f"{'排名':<6}{'学校名称':<14}{'省市':<8}{'学校类型':<10}{'总分':<8}")
    print('-' * 50)

    for univ in univ_list:
        rank, school, province, univ_type, score = univ
        # 使用全角空格补齐学校名称,保证中文对齐
        school_padded = school + '\u3000' * max(0, 12 - len(school))
        print(f"{rank:<6}{school_padded}{province:<8}{univ_type:<10}{score:<8}")


def main():
    """主函数:执行爬取、解析和打印流程"""
    url = 'http://www.shanghairanking.cn/rankings/bcur/2020'
    html = get_html_text(url)
    if not html:
        return
    univ_list = parse_university_list(html)
    print_university_list(univ_list)
    print(f'\n共爬取到 {len(univ_list)} 所大学的数据')


if __name__ == '__main__':
    main()

输出结果

屏幕截图 2026-10-09 142249

2)、心得体会

本次作业让我掌握了使用 requests 库发送HTTP请求和 BeautifulSoup 库解析HTML的核心流程。技术上的关键收获是:find、find_all 和 CSS 选择器的组合使用能够高效定位嵌套标签中的数据;strip() 方法用于去除提取文本中的空白字符,保证数据整洁。实践中遇到的困难主要是中文对齐问题——学校名称长度不一致导致输出错位,最终通过全角空格(\u3000)填充来解决。

关于爬取条数的思考:
本次实验中,程序实际爬取并打印了 30 所大学的数据,通过分析我发现:软科排名页面采用了分页动态加载技术,初始返回的 HTML 源码中只包含第一页的 30 条数据,后续数据需要点击"下一页"时由 JavaScript 异步请求接口才能获取。

本次实验使用 BeautifulSoup 直接解析初始 HTML 的方式,只获取到前 30 所大学。这次经历让我深刻认识到:BeautifulSoup 只能解析静态 HTML 标签,无法处理动态渲染的内容。这为我后续深入学习动态网页爬取提供了明确的方向。此外,通过添加 raise_for_status() 和 try-except 异常处理,程序在请求失败时能够优雅退出而不会崩溃。

作业②

1)、商城商品比价定向爬虫实验

页面结构分析

本次选择当当网作为目标商城,以关键词“书包”搜索。通过 F12 分析页面源码,确认商品名称位于 <p class="name"> 标签下的 <a> 标签的 title 属性中;商品价格位于 <span class="price_n"> 中(注意:源码中货币符号是 HTML 实体 &yen;,而不是直接显示的 ¥)。分页通过 URL 参数 page_index 控制。

核心代码

"""
作业②:使用requests和re库爬取当当网商品比价信息
搜索关键词:书包

"""

import re
import time
import random
from urllib.parse import quote

import requests


def get_html_text(url):
    """向目标URL发送GET请求,获取网页HTML源码"""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/120.0.0.0 Safari/537.36',
        'Referer': 'https://www.dangdang.com/',
        'Accept-Language': 'zh-CN,zh;q=0.9',
    }
    try:
        response = requests.get(url, headers=headers, timeout=15)
        response.raise_for_status()
        response.encoding = response.apparent_encoding
        return response.text
    except requests.RequestException as e:
        print(f'  [请求失败] {e}')
        return ''


def parse_page(item_list, html):
    """使用正则表达式从HTML源码中提取商品名称和价格"""
    # 商品名称:<p class="name"> ... <a ... title="商品名">
    title_list = re.findall(
        r'<p class="name".*?<a[^>]*title="([^"]+)"', html, re.S
    )

    # 商品价格:<span class="price_n">&yen;146.00</span>
    # 注意:源码中货币符号是 HTML 实体 &yen;,不是 ¥ 或 ¥
    price_list = re.findall(
        r'<span class="price_n">\s*&yen;\s*([\d.]+)', html
    )

    print(f'  [调试] 本页匹配到标题 {len(title_list)} 个,价格 {len(price_list)} 个')

    # 将名称与价格配对存入列表
    for i in range(min(len(title_list), len(price_list))):
        item_list.append([price_list[i], title_list[i].strip()])


def print_item_list(item_list):
    """格式化打印商品比价信息"""
    print(f"\n{'序号':<6}{'价格':<12}商品名称")
    print('-' * 70)
    for idx, item in enumerate(item_list, start=1):
        price, title = item
        # 学校名称太长时截断显示
        if len(title) > 40:
            title = title[:40] + '...'
        print(f"{idx:<6}{price:<12}{title}")


def main():
    """主函数:分页爬取当当网指定关键词的商品数据"""
    print('==== 程序开始运行 ====')

    keyword = '书包'
    base_url = 'https://search.dangdang.com/?key=' + quote(keyword) + '&act=input'
    depth = 3
    item_list = []

    for page in range(1, depth + 1):
        if page == 1:
            url = base_url
        else:
            url = base_url + '&page_index=' + str(page)

        print(f'正在爬取第 {page} 页...')
        html = get_html_text(url)

        if html:
            print(f'  [调试] 网页长度:{len(html)}')
            parse_page(item_list, html)
        else:
            print('  [错误] 未获取到网页内容')

        # 休眠
        if page < depth:  # 最后一页后不再休眠
            sleep_time = random.uniform(3, 5)
            print(f'  [休眠] 等待 {sleep_time:.2f} 秒...\n')
            time.sleep(sleep_time)

    print_item_list(item_list)
    print(f'\n共爬取到 {len(item_list)} 件商品')


if __name__ == '__main__':
    main()

输出结果

运行代码后,成功爬取了前3页共180件商品的信息,包括价格和商品名称。

屏幕截图 2026-10-09 150730
屏幕截图 2026-10-09 150741

2)、心得体会

本次作业我最初尝试了淘宝和京东,但都遇到了反爬问题——淘宝的数据隐藏在加密的接口中,而京东的搜索页已改版为 React 单页应用(SPA),初始 HTML 中根本无法提取数据。最终,我选择了当当网作为目标站点,其搜索页直接返回了包含商品数据的静态 HTML。

在开发过程中,我遇到了一个非常隐蔽的问题:正则表达式一直匹配不到价格。通过打印真实 HTML 片段,我发现了关键原因:源码里的货币符号是 HTML 实体 &yen;,而浏览器中看到的是 ¥。这让我深刻理解了“requests 获取的是原始 HTML 源码,不会执行 JavaScript,也不会自动转义 HTML 实体”这一核心知识点。

同时,我也严格限制请求频率,不尝试破解加密接口,将数据仅用于课程学习,体现了数据采集工程师应有的法律意识。


作业③

1)、网页图片文件爬取实验

页面结构分析

以福州大学新闻网“院系风采”页面( https://news.fzu.edu.cn/yxfd.htm )为目标。通过 F12 查看页面源码,图片以 <img src="..."> 标签嵌入在 HTML 中。图片文件格式包括 JPEG、JPG 和 PNG,同时也包含大量 SVG 矢量图标(如返回顶部按钮、搜索按钮)。根据作业要求,正则表达式需要严格过滤,仅保留 jpg|jpeg|png,并排除 SVG。

屏幕截图 2026-10-09 151244

核心代码

"""
作业③:爬取指定网页中所有JPEG、JPG、PNG格式图片并保存到本地文件夹
目标网页:https://news.fzu.edu.cn/yxfd.htm
"""

import os
import re
import time

import requests


def get_html_text(url):
    """
    发送HTTP请求获取网页HTML源码
    :param url: 目标网页地址
    :return: 网页HTML文本,请求失败返回空字符串
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/120.0.0.0 Safari/537.36'
    }
    try:
        response = requests.get(url, headers=headers, timeout=15)
        response.raise_for_status()
        response.encoding = response.apparent_encoding
        return response.text
    except requests.RequestException as e:
        print(f'获取网页失败:{e}')
        return ''


def extract_image_urls(html, base_url):
    """
    使用正则表达式从HTML中提取所有图片链接
    :param html: 网页HTML文本
    :param base_url: 网页基础URL,用于拼接相对路径
    :return: 图片完整URL列表
    """
    # 匹配 src="..." 中的图片链接,支持 jpg/jpeg/png 格式
    pattern = r'src="([^"]+\.(?:jpg|jpeg|png))"'
    img_urls = re.findall(pattern, html, re.IGNORECASE)

    # 处理相对路径,拼接为完整的URL
    full_urls = []
    for url in img_urls:
        if url.startswith('http://') or url.startswith('https://'):
            full_urls.append(url)
        elif url.startswith('//'):
            full_urls.append('https:' + url)
        elif url.startswith('/'):
            # 从base_url中提取协议和域名部分
            domain = base_url.split('/', 3)[:3]
            full_urls.append('/'.join(domain) + url)
        else:
            # 相对路径,与base_url所在目录拼接
            base_dir = base_url.rsplit('/', 1)[0]
            full_urls.append(base_dir + '/' + url)

    # 去重并保持顺序
    return list(dict.fromkeys(full_urls))


def download_images(img_urls, save_dir):
    """
    下载图片并保存到指定文件夹
    :param img_urls: 图片URL列表
    :param save_dir: 保存图片的本地文件夹路径
    """
    # 创建保存目录
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)
        print(f'已创建文件夹:{save_dir}')

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/120.0.0.0 Safari/537.36'
    }

    success_count = 0
    for idx, img_url in enumerate(img_urls, start=1):
        try:
            response = requests.get(img_url, headers=headers, timeout=15)
            response.raise_for_status()

            # 从URL中提取文件扩展名
            ext = img_url.split('.')[-1].lower()
            # 生成文件名:序号 + 扩展名
            filename = f'{idx:03d}.{ext}'
            filepath = os.path.join(save_dir, filename)

            # 以二进制方式写入图片文件
            with open(filepath, 'wb') as f:
                f.write(response.content)

            print(f'[{idx}/{len(img_urls)}] 已保存:{filename}')
            success_count += 1
            time.sleep(0.5)                  # 避免请求过快

        except requests.RequestException as e:
            print(f'[{idx}/{len(img_urls)}] 下载失败:{img_url},原因:{e}')

    print(f'\n下载完成:成功 {success_count} 张,失败 {len(img_urls) - success_count} 张')


def main():
    """主函数:执行图片爬取、提取和下载流程"""
    url = 'https://news.fzu.edu.cn/yxfd.htm'
    save_dir = 'downloaded_images'

    print('正在获取网页...')
    html = get_html_text(url)
    if not html:
        return

    print('正在提取图片链接...')
    img_urls = extract_image_urls(html, url)
    print(f'共找到 {len(img_urls)} 个图片链接')

    print('开始下载图片...')
    download_images(img_urls, save_dir)


if __name__ == '__main__':
    main()

输出结果

运行代码后,所有符合条件的图片均成功保存在本地 downloaded_images 文件夹中。

屏幕截图 2026-10-09 151335

屏幕截图 2026-10-09 151505

2)、心得体会

本作业的关键在于正则表达式对图片链接的提取和相对路径的处理。图片 src 属性可能是绝对路径、协议相对路径或相对路径,需要分别处理才能生成完整的可请求URL。

在实际运行中,我发现抓取的结果既包含新闻正文中的大图,也包含了网站的 Logo 和导航图标。通过重新审视作业要求“爬取所有 JPEG、JPG 或 PNG 格式图片文件”,我确认这些图标完全符合抓取标准。同时,我注意到网页中存在大量 SVG 矢量图,因此在正则中使用了 (?:jpg|jpeg|png) 进行严格过滤,成功排除了 SVG 文件。

通过本次实践,我掌握了“请求→解析→下载→保存”这一图片爬取的完整流程,也理解了文件 I/O 操作中编码模式(二进制写入 'wb')选择的重要性。


posted @ 2026-10-12 18:17  102402113  阅读(11)  评论(0)    收藏  举报