数据采集第一次作业

Python网络爬虫实验作业博客

一、作业①:定向爬取大学排名信息

1.1 作业代码与运行结果

代码实现:

# -*- coding: utf-8 -*-
"""
作业①:使用requests和BeautifulSoup定向爬取上海软科大学排名数据
目标网址:http://www.shanghairanking.cn/rankings/bcur/2020
"""

import requests
from bs4 import BeautifulSoup

def get_html(url):
    """
    获取网页HTML内容
    :param url: 目标网址
    :return: 网页文本内容
    """
    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 = response.apparent_encoding
        return response.text
    except requests.RequestException as e:
        print(f"网页获取失败:{e}")
        return None

def parse_html(html):
    """
    解析HTML,提取大学排名信息
    :param html: 网页文本
    :return: 排名信息列表
    """
    soup = BeautifulSoup(html, 'html.parser')
    # 定位排名表格主体
    table = soup.find('table', class_='rk-table')
    if not table:
        print("未找到排名表格")
        return []

    results = []
    # 遍历表格的每一行(跳过表头)
    rows = table.find('tbody').find_all('tr')
    for row in rows:
        cols = row.find_all('td')
        if len(cols) < 5:
            continue

        # 排名
        rank = cols[0].get_text(strip=True)
        # 学校名称
        school = cols[1].get_text(strip=True)
        # 省市
        province = cols[2].get_text(strip=True)
        # 学校类型
        school_type = cols[3].get_text(strip=True)
        # 总分
        score = cols[4].get_text(strip=True)

        results.append({
            'rank': rank,
            'school': school,
            'province': province,
            'type': school_type,
            'score': score
        })
    return results

def print_result(results):
    """
    格式化打印爬取结果
    :param results: 排名信息列表
    """
    print(f"{'排名':<6}{'学校名称':<20}{'省市':<10}{'学校类型':<10}{'总分':<10}")
    print("-" * 60)
    for item in results:
        print(f"{item['rank']:<6}{item['school']:<20}"
              f"{item['province']:<10}{item['type']:<10}{item['score']:<10}")

def main():
    """主函数:调度爬虫流程"""
    url = 'http://www.shanghairanking.cn/rankings/bcur/2020'
    html = get_html(url)
    if html:
        results = parse_html(html)
        print_result(results)

if __name__ == '__main__':
    main()

运行结果截图:

(此处插入运行结果截图:终端打印出清华、北大等高校排名、省市、类型、总分)

运行结果示例:

排名   学校名称            省市      学校类型  总分
------------------------------------------------------------
1    清华大学            北京      综合      852.5
2    北京大学            北京      综合      746.7
3    浙江大学            浙江      综合      649.2
4    上海交通大学        上海      综合      625.3
5    南京大学            江苏      综合      566.1
...

1.2 作业心得

本次作业让我掌握了基于 requests + BeautifulSoup 的静态网页定向爬取流程。核心步骤是:先用 requests.get() 携带 User-Agent 请求头模拟浏览器访问,解决反爬拦截问题;再将响应文本交给 BeautifulSoup,通过 find('table', class_='rk-table') 精确定位表格,用 find_all('tr') 逐行遍历,get_text(strip=True) 清洗文本。

遇到的主要困难是页面表格结构较复杂,表头与数据行需分别处理,且部分单元格内嵌套了 <a>、<span> 标签。通过观察开发者工具中的 DOM 结构,我确认了“取 tbody 下 tr,再按 td 索引取值”的方案可行。体会最深的是:爬虫开发前必须先用浏览器开发者工具分析页面结构,盲目写选择器会反复失败。此外,异常处理(try-except)与 timeout 设置提升了代码健壮性。


二、作业②:商城商品比价定向爬虫

2.1 作业代码与运行结果

代码实现(以京东搜索页为例):

# -*- coding: utf-8 -*-
"""
作业②:使用requests和re库爬取京东商城关键词"书包"的商品名称与价格
"""

import requests
import re

def get_html(keyword):
    """
    获取搜索页面HTML
    :param keyword: 搜索关键词
    :return: 网页文本
    """
    url = 'https://search.jd.com/Search'
    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.jd.com/'
    }
    params = {'keyword': keyword, 'enc': 'utf-8'}
    try:
        response = requests.get(url, headers=headers, params=params, timeout=10)
        response.raise_for_status()
        response.encoding = 'utf-8'
        return response.text
    except requests.RequestException as e:
        print(f"请求失败:{e}")
        return None

def parse_products(html):
    """
    用正则解析商品名称与价格
    :param html: 网页文本
    :return: [(价格, 商品名), ...] 列表
    """
    products = []
    # 正则匹配商品区块(每个商品 li 块)
    items = re.findall(r'<li class="gl-item".*?</li>', html, re.S)
    for item in items:
        # 匹配价格:<i>数字</i>
        price_match = re.search(r'<i>([\d.]+)</i>', item)
        # 匹配商品名称:<a ...>名称</a> 或 title 属性
        name_match = re.search(r'<div class="p-name[^"]*">.*?<em>(.*?)</em>', item, re.S)
        if price_match and name_match:
            price = price_match.group(1).strip()
            # 去除商品名中的HTML标签
            name = re.sub(r'<.*?>', '', name_match.group(1)).strip()
            products.append((price, name))
    return products

def print_result(products):
    """打印商品比价结果"""
    print(f"{'序号':<6}{'价格':<10}{'商品名'}")
    print("-" * 80)
    for idx, (price, name) in enumerate(products, start=1):
        print(f"{idx:<6}{price:<10}{name[:50]}")

def main():
    """主函数"""
    html = get_html('书包')
    if html:
        products = parse_products(html)
        print_result(products)

if __name__ == '__main__':
    main()

运行结果截图:

(此处插入运行结果截图:序号、价格、商品名三列数据)

运行结果示例:

序号   价格      商品名
------------------------------------------------------------
1     65.00     xxx双肩包男女背包
2     89.90     xxx大容量学生书包
...

2.2 作业心得

本作业我选择京东商城作为比价对象,重点练习 re 正则表达式 的使用。与 BeautifulSoup 相比,正则更适合快速提取结构化文本字段,但编写时需注意贪婪匹配陷阱,因此我使用 re.S 让 . 匹配换行符,并用 .*? 非贪婪模式截取商品区块。

难点在于:电商页面数据常由 JS 动态渲染,直接请求返回的 HTML 中商品数据可能不全或结构不一致,因此需在“商品详情页 HTML 源码”中定位 gl-item 列表。价格部分用 <i>([\d.]+)</i> 精准捕获。通过本次实践,我理解了正则的分组捕获和标签清洗(用 re.sub 去标签)技巧,也意识到正则的局限性——一旦页面改版规则就失效,实际项目更推荐 BeautifulSoup 或 lxml 配合 CSS 选择器。


三、作业③:网页图片批量爬取与保存

3.1 作业代码与运行结果

代码实现:

# -*- coding: utf-8 -*-
"""
作业③:爬取福州大学新闻网指定页面所有 JPEG/JPG/PNG 图片并保存到文件夹
"""

import os
import re
import requests
from urllib.parse import urljoin

def get_html(url):
    """
    获取网页HTML
    :param url: 页面地址
    :return: 网页文本
    """
    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 = response.apparent_encoding
        return response.text
    except requests.RequestException as e:
        print(f"网页获取失败:{e}")
        return None

def extract_image_urls(html, base_url):
    """
    用正则提取图片链接
    :param html: 网页文本
    :param base_url: 用于补全相对路径
    :return: 图片URL列表
    """
    # 匹配 <img src="..."> 中 jpg/jpeg/png 结尾(可带参数)
    pattern = r'<img[^>]+src=["\']([^"\']+\.(?:jpg|jpeg|png))(?:\?[^"\']*)?["\']'
    urls = re.findall(pattern, html, re.I)
    # 补全相对路径并去重
    full_urls = list({urljoin(base_url, u) for u in urls})
    return full_urls

def save_images(image_urls, folder='images'):
    """
    下载并保存图片
    :param image_urls: 图片URL列表
    :param folder: 保存文件夹
    """
    if not os.path.exists(folder):
        os.makedirs(folder)

    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'
    }
    count = 0
    for i, url in enumerate(image_urls, start=1):
        try:
            resp = requests.get(url, headers=headers, timeout=15)
            resp.raise_for_status()
            # 根据扩展名命名文件
            ext = url.split('.')[-1].split('?')[0].lower()
            filename = os.path.join(folder, f'image_{i}.{ext}')
            with open(filename, 'wb') as f:
                f.write(resp.content)
            count += 1
            print(f"已保存:{filename}")
        except requests.RequestException as e:
            print(f"图片下载失败 {url}:{e}")
    print(f"共保存 {count} 张图片到 {folder}/ 目录")

def main():
    """主函数"""
    url = 'https://news.fzu.edu.cn/yxfd.htm'
    html = get_html(url)
    if html:
        urls = extract_image_urls(html, url)
        print(f"共发现 {len(urls)} 张图片")
        save_images(urls, folder='fzu_images')

if __name__ == '__main__':
    main()

运行结果截图:

(此处插入运行结果截图:终端打印“已保存”信息,以及 fzu_images 文件夹中的图片文件列表)

运行结果示例:

共发现 18 张图片
已保存:fzu_images/image_1.jpg
已保存:fzu_images/image_2.png
...
共保存 18 张图片到 fzu_images/ 目录

3.2 作业心得

本作业综合运用了 requests 下载 + re 提取 + os 文件操作。关键点有三:一是用 urljoin 处理相对路径(如 /__local/xxx.jpg)补全为绝对 URL,避免下载失败;二是正则中加入 (?:\?[^"\']*)? 兼容带查询参数的图片链接;三是用 resp.content 以二进制模式写入文件,保证图片不被破坏。

实践中发现新闻网图片多为相对路径且部分重复,因此我用 set 去重,并按序号命名避免同名覆盖。遇到的主要问题是服务器对无 Referer 的请求可能返回 403,加上 UA 后解决。通过本作业我掌握了“提取链接→补全 URL→批量下载→落盘保存”的完整图片爬取闭环,也体会到 os.makedirs 目录判断与异常处理对脚本稳定性的重要性。


说明:以上博客内容可直接复制到博客园 Markdown 编辑器中,代码块使用三反引号包裹,运行结果截图位置已在文中标注(“此处插入运行结果截图”),上传图片后即可发布。如实际页面结构改版导致选择器/正则不匹配,可对照开发者工具微调后再运行。

posted @ 2026-10-15 10:22  Moliar-  阅读(7)  评论(0)    收藏  举报