数据采集第一次作业

数据采集第一次作业

作业①:大学排名信息爬取

1. 代码与运行结果

实验要求

使用 requests 和 BeautifulSoup 库,定向爬取 2020 年中国大学排名页面,并在屏幕上打印大学排名信息。输出字段为:排名、学校名称、省市、学校类型、总分。

完整代码

import urllib.request
from bs4 import BeautifulSoup

headers = {
    "User-Agent": "Mozilla/5.0"
}

def display_width(text):
    width = 0
    for char in text:
        width += 2 if ord(char) > 127 else 1
    return width

def align_text(text, width):
    text = str(text)
    return text + " " * max(0, width - display_width(text))

def get_data(url):
    req = urllib.request.Request(url, headers=headers)
    html = urllib.request.urlopen(req).read().decode("utf-8")

    soup = BeautifulSoup(html, "html.parser")
    rows = soup.find_all("tr")
    universities = []

    for row in rows:
        cols = row.find_all("td")

        if len(cols) >= 5:
            data = [c.get_text(" ", strip=True) for c in cols]

            if data[0].isdigit():
                rank = data[0]
                school = data[1].split(" ")[0]
                province = data[2]
                school_type = data[3]
                score = data[4]

                universities.append([
                    rank, school, province, school_type, score
                ])

    return universities

url = "https://www.shanghairanking.cn/rankings/bcur/2020"

universities = get_data(url)

rank_width = 8
school_width = 24
province_width = 10
type_width = 12

print(
    align_text("排名", rank_width) +
    align_text("学校名称", school_width) +
    align_text("省市", province_width) +
    align_text("学校类型", type_width) +
    "总分"
)

for university in universities:
    rank, school, province, school_type, score = university

    print(
        align_text(rank, rank_width) +
        align_text(school, school_width) +
        align_text(province, province_width) +
        align_text(school_type, type_width) +
        score
    )

运行结果

高校输出

2. 心得体会

本次实验让我初步掌握了使用 requests 获取网页源代码、使用 BeautifulSoup 解析 HTML 文档的基本流程。通过查找表格和遍历表格行,可以将网页中原本以 HTML 形式组织的数据提取出来,再以较为整齐的格式打印到终端。

在实验过程中,我也认识到网页结构会直接影响解析结果。如果网页改版、表格结构发生变化,或者服务器限制了程序访问,就可能导致数据提取失败。因此,编写爬虫时不仅要关注数据解析,还要考虑请求异常、网页结构变化和结果检查等问题。


作业②:淘宝商城商品比价爬虫

1. 代码与运行结果

实验要求

使用 requests 和 re 库设计商城商品比价定向爬虫,以“书包”为关键词,提取商品名称和价格。

本次选择淘宝商城,原始搜索页面为:书包_淘宝搜索。

淘宝页面

考虑到直接请求淘宝页面可能受到访问限制,且页面内容可能由 JavaScript 动态加载,为了便于实验和提高解析的稳定性,我先将该搜索网页下载到本地,再读取本地 HTML 文件进行解析。本题实际解析的是保存下来的网页文件,而不是每次运行时重新请求淘宝网站。

完整代码

将 taobao_spider.py 与 书包_淘宝搜索.html 放在同一个文件夹中,运行程序即可。

import re
import html
from pathlib import Path

# HTML文件路径
BASE_DIR = Path(__file__).resolve().parent
HTML_FILE = BASE_DIR / "书包_淘宝搜索.html"


# 读取本地HTML网页
def getHTMLText():
    try:
        with open(HTML_FILE, "r", encoding="utf-8",
                  errors="ignore") as f:
            return f.read()

    except FileNotFoundError:
        print("错误:没有找到淘宝搜索结果HTML文件!")
        return ""

    except Exception as e:
        print("读取网页失败:", e)
        return ""


# 提取商品名称和价格
def parsePage(source):
    goods_list = []

    # 查找淘宝商品编号
    pattern = r'id="item_id_(\d+)"'
    matches = list(re.finditer(pattern, source))

    for i, match in enumerate(matches):

        start = match.start()

        if i + 1 < len(matches):
            end = matches[i + 1].start()
        else:
            end = len(source)

        block = source[start:end]

        # 提取商品名称
        title_match = re.search(
            r'class="title--[^"]*"\s+title="([^"]*)"',
            block
        )

        # 提取价格整数部分
        price_int_match = re.search(
            r'class="priceInt--[^"]*">([^<]*)</div>',
            block
        )

        # 提取价格小数部分
        price_float_match = re.search(
            r'class="priceFloat--[^"]*">([^<]*)</div>',
            block
        )

        if not title_match or not price_int_match:
            continue

        name = html.unescape(title_match.group(1)).strip()

        price_int = price_int_match.group(1).strip()

        price_float = ""

        if price_float_match:
            price_float = price_float_match.group(1).strip()

        try:
            price = float(price_int + price_float)
        except ValueError:
            continue

        if name:
            goods_list.append({
                "id": match.group(1),
                "name": name,
                "price": price
            })

    return goods_list


# 输出商品信息
def printGoods(goods_list):

    print("\n" + "=" * 90)
    print("                 淘宝商城商品比价结果")
    print("                    搜索关键词:书包")
    print("=" * 90)

    print("{:<8}{:<18}{}".format(
        "序号", "商品价格(元)", "商品名称"
    ))

    print("-" * 90)

    for i, goods in enumerate(goods_list, 1):

        print("{:<8}{:<18.2f}{}".format(
            i,
            goods["price"],
            goods["name"]
        ))

    print("=" * 90)

    print("成功获取商品数量:", len(goods_list), "项")

    if goods_list:
        print("商品最低价格:{:.2f}元".format(
            min(g["price"] for g in goods_list)
        ))

        print("商品最高价格:{:.2f}元".format(
            max(g["price"] for g in goods_list)
        ))

        print("商品平均价格:{:.2f}元".format(
            sum(g["price"] for g in goods_list)
            / len(goods_list)
        ))


# 主函数
def main():

    print("开始读取淘宝商城搜索结果")
    print("搜索关键词:书包")

    source = getHTMLText()

    if not source:
        return

    print("网页读取成功!")

    goods_list = parsePage(source)

    print("网页解析完成!")

    printGoods(goods_list)

    print("\n实验任务完成!")


if __name__ == "__main__":
    main()

运行结果

淘宝输出

2. 心得体会

本次实验让我了解了正则表达式在网页信息提取中的应用。与直接输出整个网页相比,通过正则表达式匹配商品编号、名称和价格,可以将需要的信息从大量 HTML 代码中分离出来。

由于商品名称和价格位于同一商品对应的 HTML 数据块中,我先定位商品编号,再截取当前商品的数据范围,最后分别提取名称和价格。这种处理方式能够降低不同商品信息混淆的风险。

此外,本次实验采用本地 HTML 文件进行解析,使网页获取与数据提取两个步骤相互独立。这样既便于重复调试,也能减少频繁访问网站带来的不确定性。不过,本地网页只是保存时的页面快照,数据可能已经过时;如果淘宝页面的 HTML 结构发生变化,正则表达式也需要相应调整。


作业③:福州大学网页图片爬取

1. 代码与运行结果

实验要求

爬取福州大学院系风采页面 https://news.fzu.edu.cn/yxfd.htm 中的图片,将 JPEG、JPG 或 PNG 格式的图片文件保存到一个文件夹中。

完整代码

本程序使用 requests 获取网页和图片内容,使用 BeautifulSoup 解析图片标签,并根据图片 URL 的文件扩展名筛选目标图片格式。

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from pathlib import Path


# 目标网页和图片保存目录
URL = "https://news.fzu.edu.cn/yxfd.htm"
SAVE_DIR = Path(__file__).resolve().parent / "fzu_images"

# 需要下载的图片格式
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png"}


def get_html(url):
    """请求目标网页并返回 HTML 文本。"""
    headers = {
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/128.0.0.0 Safari/537.36"
        )
    }

    response = requests.get(url, headers=headers, timeout=15)
    response.raise_for_status()
    response.encoding = response.apparent_encoding
    return response.text


def extract_image_urls(html_text, page_url):
    """解析网页,提取 JPEG、JPG 和 PNG 格式的图片链接。"""
    soup = BeautifulSoup(html_text, "html.parser")
    image_urls = set()

    for image in soup.find_all("img"):
        # 兼容常见的图片地址属性
        candidates = [
            image.get("src"),
            image.get("data-src"),
            image.get("data-original")
        ]

        # 兼容 srcset 中提供的图片地址
        srcset = image.get("srcset")
        if srcset:
            for item in srcset.split(","):
                parts = item.strip().split()
                if parts:
                    candidates.append(parts[0])

        for candidate in candidates:
            if not candidate:
                continue

            # 将相对地址转换为绝对地址
            image_url = urljoin(page_url, candidate.strip())
            path = urlparse(image_url).path.lower()

            # 只保留题目要求的图片格式
            if Path(path).suffix in IMAGE_EXTENSIONS:
                image_urls.add(image_url)

    return sorted(image_urls)


def download_images(image_urls):
    """下载图片并保存到指定文件夹。"""
    SAVE_DIR.mkdir(parents=True, exist_ok=True)

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

    success_count = 0

    with requests.Session() as session:
        for index, image_url in enumerate(image_urls, start=1):
            try:
                response = session.get(
                    image_url, headers=headers, timeout=15
                )
                response.raise_for_status()

                # 从 URL 中取得扩展名,避免保存成错误的格式
                suffix = Path(urlparse(image_url).path).suffix.lower()
                file_path = SAVE_DIR / f"image_{index:03d}{suffix}"

                with open(file_path, "wb") as file:
                    file.write(response.content)

                success_count += 1
                print(f"已保存:{file_path.name}")

            except requests.RequestException as error:
                print(f"下载失败:{image_url}")
                print("原因:", error)

    print(f"\n网页中发现符合格式的图片链接:{len(image_urls)} 个")
    print(f"成功下载图片:{success_count} 张")
    print(f"图片保存目录:{SAVE_DIR}")


def main():
    """程序入口。"""
    try:
        html_text = get_html(URL)
        image_urls = extract_image_urls(html_text, URL)

        if not image_urls:
            print("未找到符合要求的图片,请检查网页结构或图片地址。")
            return

        download_images(image_urls)

    except requests.RequestException as error:
        print("网页请求失败:", error)


if __name__ == "__main__":
    main()

运行结果

运行程序后,程序会先解析网页中的图片地址,再依次下载符合格式要求的图片。成功下载的文件会保存在脚本所在目录下的 fzu_images 文件夹中,文件名按照 image_001.jpg、image_002.png 等形式生成。

程序会输出发现的图片链接数量、成功下载数量和保存目录。需要注意,发现的图片链接数不一定等于成功下载的图片数,因为部分图片可能无法访问,或者服务器可能限制请求。

新闻网输出

图片文件夹截图:

爬取的图片

2. 心得体会

本次实验让我掌握了网页图片爬取的基本流程:先请求网页,再解析图片标签,接着提取图片 URL,最后下载图片并保存到本地文件夹。

在处理图片链接时,需要注意相对路径与绝对路径的区别,因此使用 urljoin 将相对地址转换为完整 URL。同时,通过 urlparse 获取 URL 路径的扩展名,可以筛选出题目要求的 JPEG、JPG 和 PNG 格式图片。

实际网页中的图片不一定都能成功下载。例如,图片地址可能为空、图片资源可能失效,或者服务器拒绝访问。因此,程序对请求异常进行了处理,并分别统计发现的图片链接数量和实际下载成功的数量。

通过这三道题,我对 Python 爬虫的基本流程有了更完整的认识:获取网页、解析 HTML、提取目标数据以及保存结果。不同网页的结构各不相同,需要结合具体页面选择合适的解析方法,同时编写必要的异常处理代码,才能使程序更可靠。

posted @ 2026-10-12 19:32  Emmamone  阅读(7)  评论(0)    收藏  举报