数据采集第一次作业

Python网络爬虫作业


作业①:大学排名数据爬取

一、代码与运行结果

要求:用requests和BeautifulSoup库方法定向爬取给定网址( http://www.shanghairanking.cn/rankings/bcur/2020 )。

完整代码:

"""
作业①: 爬取2020年中国大学排名
使用requests和BeautifulSoup库
目标网址: http://www.shanghairanking.cn/rankings/bcur/2020
输出: 排名、学校名称、省市、学校类型、总分
"""
import requests
from bs4 import BeautifulSoup

URL = "http://www.shanghairanking.cn/rankings/bcur/2020"

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",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9",
}


def fetch_page(url):
    """
    使用requests发送HTTP请求获取页面内容
    """
    try:
        response = requests.get(url, headers=HEADERS, timeout=30)
        response.encoding = response.apparent_encoding
        if response.status_code == 200:
            return response.text
        else:
            print("请求失败,状态码: %d" % response.status_code)
            return None
    except Exception as e:
        print("请求异常: %s" % e)
        return None


def parse_university_data(html):
    """
    使用BeautifulSoup解析大学排名表格
    提取: 排名、学校名称、省市、学校类型、总分
    """
    if not html:
        return []

    soup = BeautifulSoup(html, "html.parser")
    table = soup.find("table", class_="rk-table")

    if table is None:
        print("未找到排名表格")
        return []

    universities = []
    tbody = table.find("tbody")
    if tbody:
        rows = tbody.find_all("tr")
        for row in rows:
            tds = row.find_all("td")
            if len(tds) < 5:
                continue
            name_span = tds[1].find("span", class_="name-cn")
            universities.append({
                "rank": tds[0].get_text(strip=True),
                "name": name_span.get_text(strip=True) if name_span else "",
                "province": tds[2].get_text(strip=True),
                "category": tds[3].get_text(strip=True),
                "score": tds[4].get_text(strip=True),
            })

    return universities


def print_universities(universities):
    """
    格式化打印大学排名信息
    """
    if not universities:
        print("未获取到数据")
        return

    print("\n2020 软科中国大学排名")
    print("-" * 78)
    print("排名\t" + "学校名称\t" + "省市\t" + "类型\t" + "总分")
    print("-" * 78)
    for r in universities:
        print(r["rank"] + "\t" + r["name"] + "\t" + r["province"]
              + "\t" + r["category"] + "\t" + r["score"])
    print("-" * 78)
    print("共 %d 条" % len(universities))


def main():
    print("正在爬取:%s" % URL)
    html = fetch_page(URL)
    if html:
        universities = parse_university_data(html)
        print("页面表格解析到 %d 条记录" % len(universities))
        print_universities(universities)
    else:
        print("页面获取失败")


if __name__ == "__main__":
    main()

运行结果截图:

image


二、作业心得

本次作业主要学习了如何使用requests库发送HTTP请求和BeautifulSoup库解析HTML文档。

关键技术点:

  1. requests库的使用:通过requests.get()方法发送GET请求,设置headers模拟浏览器访问,避免被反爬虫机制拦截。使用timeout参数设置超时时间,防止请求长时间挂起。

  2. 网页编码处理:使用response.apparent_encoding自动检测网页编码,而不是硬编码为utf-8,避免中文乱码问题。

  3. BeautifulSoup解析HTML:使用BeautifulSoup(html, "html.parser")创建解析器对象,通过find()和find_all()方法定位HTML元素。find()返回第一个匹配的元素,find_all()返回所有匹配的元素列表。

  4. 数据提取技巧:使用get_text(strip=True)方法获取元素文本并自动去除首尾空白字符。对于嵌套元素,可以链式调用find()方法逐层定位。

遇到的问题与解决:

  • 初始运行时发现中文显示为乱码,后来发现需要使用response.apparent_encoding自动检测网页编码,而不是硬编码为utf-8。
  • 表格数据解析时,需要注意HTML结构中可能存在空行或格式不一致的情况,通过判断td标签数量来过滤无效行。

收获:掌握了网页结构分析的基本方法,学会了使用浏览器开发者工具查看HTML源码,理解了DOM树的结构和遍历方式。


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

一、代码与运行结果

要求:用requests和re库方法设计某个商城商品比价定向爬虫,爬取该商城以关键词"书包"搜索页面的数据,爬取商品名称和价格。

完整代码:

"""
作业②: 商城商品比价定向爬虫
使用requests和re库
目标商城: 当当网
爬取关键词"书包"的商品名称和价格
输出: 序号、价格、商品名
"""
import requests
import re
import time
from urllib.parse import quote

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",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "keep-alive",
}


def fetch_page(url):
    """
    使用requests发送HTTP请求获取页面内容
    """
    try:
        response = requests.get(url, headers=HEADERS, timeout=30)
        try:
            html = response.content.decode("utf-8")
        except UnicodeDecodeError:
            html = response.content.decode("gbk")
        if response.status_code == 200:
            return html
        else:
            print("请求失败,状态码: %d" % response.status_code)
            return None
    except Exception as e:
        print("请求异常: %s" % e)
        return None


def parse_products(html):
    """
    使用正则表达式解析商品名称和价格
    """
    if not html:
        return []

    products = []

    # 匹配商品名称(从title属性提取)
    name_pattern = r'<p[^>]*class="name"[^>]*>\s*<a[^>]*title="([^"]*)"'

    # 匹配价格
    price_pattern = r'<span[^>]*class="price_n"[^>]*>&yen;\s*([0-9]+\.?[0-9]*)</span>'

    # 查找所有名称
    names = re.findall(name_pattern, html, re.DOTALL)

    # 查找所有价格
    prices = re.findall(price_pattern, html, re.DOTALL)

    # 合并名称和价格
    for i in range(min(len(names), len(prices))):
        name = names[i].strip()
        price = prices[i].strip()
        if name and price:
            products.append({
                "name": name,
                "price": price,
            })

    return products


def scrape_dangdang(keyword="书包", max_items=60):
    """
    爬取当当网商品数据
    """
    print("=" * 60)
    print("开始爬取当当网 - 关键词: '%s'" % keyword)
    print("目标爬取数量: %d 项" % max_items)
    print("=" * 60)

    all_products = []
    page = 1
    items_per_page = 60

    while len(all_products) < max_items:
        encoded_keyword = quote(keyword)
        url = "http://search.dangdang.com/?key=%s&act=input&page_index=%d" % (encoded_keyword, page)

        print("\n正在爬取第 %d 页..." % page)
        print("URL: %s" % url)

        html = fetch_page(url)

        if html is None:
            print("获取页面失败,停止爬取")
            break

        products = parse_products(html)

        if not products:
            print("未找到商品数据,可能已到达最后一页")
            break

        print("本页找到 %d 件商品" % len(products))

        for product in products:
            if len(all_products) >= max_items:
                break
            all_products.append(product)

        page += 1

        # 礼貌延迟,避免请求过快
        time.sleep(1)

        if len(products) < items_per_page:
            print("已到达最后一页")
            break

    return all_products


def display_products(products):
    """
    格式化显示商品信息
    """
    print("\n" + "=" * 80)
    print("爬取结果汇总 - 共 %d 件商品" % len(products))
    print("=" * 80)
    print("%-6s %-50s %-10s" % ("序号", "商品名称", "价格(元)"))
    print("-" * 80)

    for idx, product in enumerate(products, 1):
        name = product["name"]
        price = product["price"]
        if len(name) > 48:
            name = name[:45] + "..."
        print("%-6d %-50s ¥%-10s" % (idx, name, price))

    print("=" * 80)

    if products:
        prices = [float(p["price"]) for p in products if p["price"].replace(".", "").isdigit()]
        if prices:
            print("\n价格统计:")
            print("  最低价: ¥%.2f" % min(prices))
            print("  最高价: ¥%.2f" % max(prices))
            print("  平均价: ¥%.2f" % (sum(prices) / len(prices)))


def save_to_file(products, filename="书包_商品信息.txt"):
    """
    保存商品信息到文件
    """
    with open(filename, "w", encoding="utf-8") as f:
        f.write("书包商品信息 - 共%d件\n" % len(products))
        f.write("=" * 80 + "\n")
        f.write("%-6s %-50s %-10s\n" % ("序号", "商品名称", "价格(元)"))
        f.write("-" * 80 + "\n")

        for idx, product in enumerate(products, 1):
            f.write("%-6d %-50s ¥%-10s\n" % (idx, product["name"], product["price"]))

        f.write("=" * 80 + "\n")

    print("\n数据已保存到: %s" % filename)


def main():
    keyword = "书包"
    max_items = 60

    products = scrape_dangdang(keyword, max_items)

    if products:
        display_products(products)
        save_to_file(products)
    else:
        print("未获取到任何商品数据")


if __name__ == "__main__":
    main()

运行结果截图:

image


二、作业心得

本次作业重点学习了正则表达式在网页数据提取中的应用。

关键技术点:

  1. 正则表达式基础:

    • r'...':原始字符串,避免反斜杠转义问题
    • [^>]*:匹配任意数量的非>字符
    • ([^"]*):捕获组,提取引号内的内容
    • re.DOTALL:使.匹配包括换行符在内的所有字符
  2. URL编码:使用urllib.parse.quote()对中文关键词进行URL编码,确保URL格式正确。

  3. 分页爬取:通过循环递增page_index参数实现多页数据爬取,使用max_items控制爬取总量。

  4. 编码处理:当当网使用GBK编码,通过response.content.decode("utf-8")尝试解码,失败时回退到gbk解码,避免中文乱码。

  5. 礼貌爬取:使用time.sleep(1)设置请求间隔,避免对目标服务器造成过大压力。

正则表达式解析:

  • 商品名称正则:<p[^>]*class="name"[^>]*>\s*<a[^>]*title="([^"]*)"

    • 匹配<p>标签中class包含"name"的元素
    • 查找其子元素<a>标签的title属性
    • 捕获引号内的商品名称
  • 价格正则:<span[^>]*class="price_n"[^>]*>&yen;\s*([0-9]+\.?[0-9]*)</span>

    • 匹配<span>标签中class为"price_n"的元素
    • 提取&yen;(¥符号)后的数字
    • 支持整数和小数格式

遇到的问题与解决:

  • 中文关键词直接拼接到URL会导致编码错误,需要使用quote()函数进行URL编码。
  • 部分商品页面结构可能不一致,通过判断名称和价格列表长度取最小值来保证数据完整性。
  • 网页编码问题:当当网使用GBK编码,需要先尝试utf-8解码,失败时回退到gbk解码,否则中文会显示为乱码。
  • 正则表达式编写需要仔细分析HTML源码结构,使用浏览器开发者工具查看元素很方便。

收获:掌握了正则表达式的基本语法和常用模式,理解了正则表达式与BeautifulSoup的适用场景差异。正则表达式适合提取结构简单、规律性强的数据,而BeautifulSoup更适合处理复杂的DOM结构。


作业③:网页图片爬取

一、代码与运行结果

要求:爬取给定网页( https://news.fzu.edu.cn/yxfd.htm )的所有JPEG、JPG或PNG格式图片文件,保存在一个文件夹中。

完整代码:

"""
作业③: 爬取网页图片
使用requests和re库
目标网址: https://news.fzu.edu.cn/yxfd.htm
输出: 将所有JPEG、JPG、PNG格式图片保存到指定文件夹
"""
import os
import re
import requests
from urllib.parse import urljoin


def fetch_page(url):
    """
    使用requests发送HTTP请求获取页面内容
    """
    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",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.9",
    }
    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.encoding = response.apparent_encoding
        if response.status_code == 200:
            return response.text
        else:
            print("请求失败,状态码: %d" % response.status_code)
            return None
    except Exception as e:
        print("请求异常: %s" % e)
        return None


def extract_image_urls(html, base_url):
    """
    使用正则表达式提取页面中所有JPEG、JPG、PNG格式图片的URL
    """
    if not html:
        return []

    # 匹配img标签中的src属性,支持jpg、jpeg、png格式
    img_pattern = r'<img[^>]+src=["\']([^"\']*\.(?:jpg|jpeg|png|JPG|JPEG|PNG))["\']'

    # 查找所有匹配的图片URL
    relative_urls = re.findall(img_pattern, html, re.IGNORECASE)

    # 将相对路径转换为绝对路径
    absolute_urls = []
    for url in relative_urls:
        absolute_url = urljoin(base_url, url)
        absolute_urls.append(absolute_url)

    return absolute_urls


def download_images(image_urls, save_dir):
    """
    下载图片并保存到指定文件夹
    """
    if not image_urls:
        print("未找到图片URL")
        return 0

    # 创建保存目录
    os.makedirs(save_dir, exist_ok=True)

    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",
    }

    downloaded_count = 0

    for i, img_url in enumerate(image_urls, 1):
        try:
            print("正在下载第 %d 张图片: %s" % (i, img_url))

            response = requests.get(img_url, headers=headers, timeout=30)

            if response.status_code == 200:
                # 从URL中提取文件名,如果没有则使用序号命名
                filename = img_url.split("/")[-1].split("?")[0]
                if not filename or not re.search(r'\.(jpg|jpeg|png)$', filename, re.IGNORECASE):
                    ext = img_url.split(".")[-1].split("?")[0].lower()
                    if ext not in ["jpg", "jpeg", "png"]:
                        ext = "jpg"
                    filename = "image_%d.%s" % (i, ext)

                filepath = os.path.join(save_dir, filename)

                # 如果文件名重复,添加序号
                if os.path.exists(filepath):
                    name, ext = os.path.splitext(filename)
                    filepath = os.path.join(save_dir, "%s_%d%s" % (name, i, ext))

                with open(filepath, "wb") as f:
                    f.write(response.content)

                print("  已保存: %s" % filename)
                downloaded_count += 1
            else:
                print("  下载失败,状态码: %d" % response.status_code)

        except Exception as e:
            print("  下载异常: %s" % e)

    return downloaded_count


def main():
    url = "https://news.fzu.edu.cn/yxfd.htm"
    save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fzu_images")

    print("=" * 70)
    print("开始爬取网页图片: %s" % url)
    print("图片保存目录: %s" % save_dir)
    print("=" * 70)

    # 获取页面内容
    print("\n正在获取页面内容...")
    html = fetch_page(url)

    if not html:
        print("页面获取失败")
        return

    # 提取图片URL
    print("正在提取图片URL...")
    image_urls = extract_image_urls(html, url)
    print("共找到 %d 张图片" % len(image_urls))

    if not image_urls:
        print("未找到图片")
        return

    # 下载图片
    print("\n开始下载图片...")
    count = download_images(image_urls, save_dir)

    print("\n" + "=" * 70)
    print("下载完成! 共下载 %d/%d 张图片" % (count, len(image_urls)))
    print("保存位置: %s" % save_dir)
    print("=" * 70)


if __name__ == "__main__":
    main()

运行结果截图:

image

下载的图片文件夹:
image

image


二、作业心得

本次作业学习了如何从网页中提取并下载图片文件。

关键技术点:

  1. 图片URL提取:使用正则表达式<img[^>]+src=["\']([^"\']*\.(?:jpg|jpeg|png|JPG|JPEG|PNG))["\']匹配<img>标签中的src属性,并通过re.IGNORECASE实现大小写不敏感匹配。

  2. 相对路径转换:网页中的图片URL可能是相对路径,使用urllib.parse.urljoin(base_url, relative_url)将相对路径转换为绝对路径,确保可以正确访问图片资源。

  3. 二进制文件下载:图片是二进制文件,使用requests.get()获取响应后,通过response.content获取二进制数据(而非response.text),以"wb"(写入二进制)模式保存到本地文件。

  4. 文件命名与去重:从URL中提取文件名,处理URL参数(?后面的内容)。如果文件名重复,添加序号避免覆盖。

  5. 目录创建:使用os.makedirs(save_dir, exist_ok=True)创建保存目录,exist_ok=True参数确保目录已存在时不会报错。

正则表达式解析:

  • <img[^>]+:匹配<img标签开始,后跟至少一个非>字符
  • src=["\']:匹配src=后的引号(单引号或双引号)
  • ([^"\']*\.(?:jpg|jpeg|png|JPG|JPEG|PNG)):捕获组,匹配以图片扩展名结尾的URL
    • (?:...):非捕获组,仅用于分组
  • ["\']:匹配结束引号

遇到的问题与解决:

  • 部分图片URL是相对路径,直接使用无法访问,需要使用urljoin()转换为绝对路径。
  • 文件名可能包含URL参数(如?v=123),需要使用split("?")[0]去除参数部分。
  • 下载二进制文件时必须使用response.content而非response.text,否则会导致文件损坏。
  • 部分图片文件名相同,需要添加序号避免覆盖。

收获:掌握了网页图片提取和下载的完整流程,理解了相对路径与绝对路径的区别,学会了处理二进制文件的正确方法。同时认识到网络爬虫需要遵守网站的robots.txt协议,合理控制请求频率,避免对目标服务器造成过大压力。


总结

通过本次作业,系统学习了Python网络爬虫的三种常用技术组合:

  1. requests + BeautifulSoup:适合解析结构化的HTML表格和DOM树
  2. requests + re:适合提取规律性强的文本数据,如价格、名称等
  3. requests + re + 文件操作:适合批量下载网页资源,如图片、文件等

核心技能提升:

  • 熟练使用requests库发送HTTP请求和处理响应
  • 掌握BeautifulSoup的find()、find_all()、get_text()等常用方法
  • 理解正则表达式的基本语法和常用模式
  • 学会使用浏览器开发者工具分析网页结构
  • 了解网络爬虫的基本规范和注意事项

注意事项:

  • 设置合理的User-Agent请求头
  • 正确处理网页编码(response.apparent_encoding或response.content.decode())
  • 添加异常处理机制
  • 控制请求频率,避免对服务器造成压力
  • 遵守网站的robots.txt协议
  • 仅将爬虫技术用于学习和合法用途
posted @ 2026-10-15 22:44  zsp-  阅读(4)  评论(0)    收藏  举报