資料採集與融合技術作業一

清风拂山岗(小高同学)發表於2024-10-15

作業1

倉庫連結:https://gitee.com/jyppx000/crawl_project

作業①【結合flask】

要求:用requests和BeautifulSoup庫方法定向爬取給定網址(http://www.shanghairanking.cn/rankings/bcur/2020)的資料,螢幕列印爬取的大學排名資訊。

1.1 程式碼和圖片

import re
import urllib.request
from bs4 import BeautifulSoup
from flask import Flask, render_template_string

class UniversityRankingScraper:
    """負責抓取網頁內容並將資料解析為HTML表格"""
    def __init__(self, url):
        self.url = url  # 儲存排名頁面的URL
        self.table_html = None  # 儲存生成的HTML表格


    def fetch_data(self):
        """傳送HTTP請求並使用BeautifulSoup解析HTML資料"""
        response = urllib.request.urlopen(self.url)
        web_content = response.read()
        soup = BeautifulSoup(web_content, 'html.parser')
        return soup

    def generate_html_table(self, soup):
        """從HTML頁面提取排名資料,並生成一個帶有表頭的HTML表格"""
        table = soup.find('table')
        html_table = '<table border="1" cellspacing="0" cellpadding="5">\n'
        html_table += '  <tr><th>排名</th><th>學校名稱</th><th>省市</th><th>學校型別</th><th>總分</th></tr>\n'


        for row in table.find_all('tr')[1:]:
            cols = row.find_all('td')
            rank = cols[0].get_text(strip=True)
            school_name = re.sub(r'[A-Za-z]|(雙一流|985|211)|/+', '', cols[1].get_text(strip=True)).strip()
            province = cols[2].get_text(strip=True)
            school_type = cols[3].get_text(strip=True)
            score = cols[4].get_text(strip=True)

            # 將提取到的資訊新增到HTML表格中
            html_table += f'  <tr><td>{rank}</td><td>{school_name}</td><td>{province}</td><td>{school_type}</td><td>{score}</td></tr>\n'

        html_table += '</table>'
        self.table_html = html_table
        return self.table_html


    def get_html_table(self):
        """生成表格方法"""
        soup = self.fetch_data()  # 獲取網頁內容
        return self.generate_html_table(soup)  # 生成並返回HTML表格



class UniversityRankingApp:
    """建立一個簡單的Flask應用,展示解析後的資料表格"""
    def __init__(self, scraper):
        self.scraper = scraper
        self.app = Flask(__name__)

        # 設定路由
        @self.app.route('/')
        def index():
            table_html = self.scraper.get_html_table()  # 獲取HTML表格
            return render_template_string(table_html)  # 渲染表格到網頁

    def run(self):
        self.app.run(debug=True)


# 入口
if __name__ == '__main__':
    # 排名URL
    url = "https://www.shanghairanking.cn/rankings/bcur/2021"
    scraper = UniversityRankingScraper(url)

    app = UniversityRankingApp(scraper)

    app.run()

1.2 作業心得

  • 鞏固了面對結構複雜的網頁時,如何使用標籤選擇和正規表示式進行精準的內容提取。
  • 進一步提升了我的資料解析和清晰能力,如:學校名稱中的“985”“211”等標籤。
  • 快速學習瞭如何將資料結構化,並透過Python將其轉化為動態HTML展示出來,特別是在Flask框架中,使用render_template_string直接渲染HTML。
  • 進一步鞏固了我個人物件導向的能力,我透過將爬蟲邏輯和Flask應用封裝在類中,使程式碼結構清晰、易於擴充套件,同時我也是我以後寫程式碼不斷在靠攏的方向。

作業②【結合django】

要求:用requests和re庫方法設計某個商城(自已選擇)商品比價定向爬蟲,爬取該商城,以關鍵詞“書包”搜尋頁面的資料,爬取商品名稱和價格。

2.1 程式碼和圖片

```settings.py``

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    "api.apps.ApiConfig"			 # 這裡是註冊的app
]

urls.py

urlpatterns = [
     path("shop/",views.shop)
]

views.py

from django.shortcuts import render

from utils.handle import  fetch_data
from utils.handle import  parse_products
from utils.handle import  extract_product_list


def shop(request):
    """執行爬取任務"""
    url = r'https://search.dangdang.com/?key=%D3%B2%C5%CC&act=input'
    data = fetch_data(url)
    if data:
        product_list = extract_product_list(data)
        data = parse_products(product_list)
    else:
        data = []
    return render(request,'index.html', {"data":data})

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>商品比價</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th {
            background-color: #f4f4f4;
        }
        tr:nth-child(even) {
            background-color: #f9f9f9;
        }
    </style>
</head>
<body>
    <h1>硬碟比價結果</h1>
    <table>
        <thead>
            <tr>
                <th>序號</th>
                <th>價格 (¥)</th>
                <th>商品名稱</th>

            </tr>
        </thead>
        <tbody>
            {% if data %}
                {% for item in data %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ item.0 }}</td>
                        <td>{{ item.1 }}</td>
                    </tr>
                {% endfor %}
            {% else %}
                <tr>
                    <td colspan="3">暫無資料</td>
                </tr>
            {% endif %}
        </tbody>
    </table>
</body>
</html>

utils/handle.py

import re
import requests

def fetch_data(url):
    """
    傳送GET請求並返回響應的HTML文字
    :param url: 請求的網址
    :return: 網頁的HTML內容,如果請求失敗則返回None
    """
    try:
        req = requests.get(url)
        req.raise_for_status()
        req.encoding = req.apparent_encoding
        return req.text
    except Exception as e:
        print("Error in request:", e)
        return None

def extract_product_list(data):
    """
    使用正規表示式從網頁資料中提取商品列表部分
    :param data: 網頁HTML內容
    :return: 提取出的商品列表HTML片段
    """
    match = re.search(r'<ul class="bigimg cloth_shoplist".*?>(.*?)</ul>', data, re.S)
    if not match:
        print("未找到商品列表")
        return []
    return match.group(1)

def parse_products(data):
    """
    解析商品資料,提取商品的名稱和價格
    :param data: 商品列表的HTML片段
    :return: 包含商品資訊的列表,每個商品是一個元組(price, name)
    """
    items = re.findall(r'<li.*?>(.*?)</li>', data, re.S)
    products = []
    for item in items:
        price_match = re.search(r'<span class="price_n">&yen;(.*?)</span>', item)
        title_match = re.search(r'title="(.*?)"', item)
        if price_match and title_match:
            price = price_match.group(1).strip()
            name = title_match.group(1).strip()
            products.append((price, name))
        else:
            products.append((None, None))  # 若找不到價格或名稱,新增空值
    return products

2.2 作業心得

  • 仍然是鞏固正則,對文字處理一個過程,當然也會存在匹配模式不準確導致無法提取資料,但是經過反覆的除錯,是能解決的

  • 對處理異常更加熟練了 !

作業③:

要求:爬取一個給定網頁( https://news.fzu.edu.cn/yxfd.htm)或者自選網頁的所有JPEG和JPG格式檔案

3.1程式碼和圖片

import os
import re
import requests
from bs4 import BeautifulSoup

def create_directory(dir_path):
    """建立儲存目錄
    :param dir_path: 目錄路徑
    """
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)


def clean_filename(filename):
    """清理檔名,去掉不合法字元
    :param filename: 原始檔名
    :return: 清理後的檔名
    """
    return re.sub(r'[<>:"/\\|?*]', '', filename)  # 去除不合法字元


def download_image(url, save_path):
    """下載圖片並儲存到指定路徑
    :param url: 圖片的URL地址
    :param save_path: 儲存路徑
    """
    try:
        res = requests.get(url, headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
        })
        with open(save_path, mode="wb") as fj:
            fj.write(res.content)
        print(f"下載成功:{save_path}")
    except Exception as e:
        print(f"下載失敗:{url},錯誤資訊:{e}")


def fetch_images_from_page(page_num):
    """從指定頁面獲取圖片並返回圖片連結和型別
    :param page_num: 頁碼
    :return: 圖片連結和檔名的元組列表
    """
    response = requests.get(
        url=f"https://news.fzu.edu.cn/yxfd.htm?page={page_num}",
        headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
        }
    )
    soup = BeautifulSoup(response.text, "html.parser")
    img_tags = soup.find_all("img")  # 找到所有圖片標籤

    image_info = []  # 儲存圖片資訊的列表
    for img in img_tags:
        img_src = img.get("src")  #
        if img_src and img_src.startswith("/"):  # 過濾掉不合法URL
            url_path = f"https://news.fzu.edu.cn{img_src}"
            file_name = clean_filename(img_src.split('/')[-1])
            image_info.append((url_path, file_name))

    return image_info


def save_images(image_info, jpeg_dir, other_dir):
    """根據圖片型別儲存圖片到不同目錄
    :param image_info: 圖片資訊列表
    :param jpeg_dir: JPEG圖片儲存目錄
    :param other_dir: 其他型別圖片儲存目錄
    """
    for url, file_name in image_info:
        if file_name.lower().endswith(('.jpeg', '.jpg')):  #
            save_path = os.path.join(jpeg_dir, file_name)  # JPEG檔案路徑
        else:
            save_path = os.path.join(other_dir, file_name)  # 其他檔案路徑
        download_image(url, save_path)


def main():
    """主函式,控制程式流程"""
    BASE_PATH = os.path.dirname(os.path.abspath(__file__))
    JPEG_DIR = os.path.join(BASE_PATH, "jpeg_images")
    OTHER_DIR = os.path.join(BASE_PATH, "other_images")

    create_directory(JPEG_DIR)
    create_directory(OTHER_DIR)

    num_pages = int(input("請輸入要爬取的頁數:"))

    for page in range(1, num_pages + 1):
        print(f"開始爬取第 {page} 頁...")
        image_info = fetch_images_from_page(page)
        save_images(image_info, JPEG_DIR, OTHER_DIR)

    print("所有頁面下載完成!")


if __name__ == "__main__":
    main()

3.2 作業心得

  • 再次熟悉正則和BeautifulSoup進行資料提取
  • 處理檔案下載時的異常情況
  • 熟悉檔案操作,os模組內建方法算是真記住了!

相關文章