数据采集实践第四次作业—102302131陈宇新

giteehttps://gitee.com/chenyuxin0328/data-collection/tree/master/作业4

作业1

熟练掌握 Selenium 查找 HTML 元素、爬取 Ajax 网页数据、等待 HTML 元素等内容。
使用 Selenium 框架+ MySQL 数据库存储技术路线爬取“沪深 A 股”、“上证 A 股”、“深证 A 股”3 个板块的股票数据信息。

代码思路讲解

这部分定义了爬虫的环境配置(数据库凭证、目标 URL)和数据结构。connect_db 函数利用 pymysql 建立到本地 MySQL 服务器的连接,是实现数据持久化的先决条件。

# --- 数据库配置 (已修正密码) ---
DB_HOST = 'localhost'
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWORD = 'mysql123'  
DB_NAME = 'stock_data_db'  

# --- 网页配置 ---
URL = 'http://quote.eastmoney.com/center/gridlist.html#hs_a_board' 
TABLE_CONTAINER_CLASS = 'quotetable'

# --- 字段设计 ---
FIELD_NAMES_EN = ["id", "bStockNo", "bStockName", "nLatestPrice", "nChangeRatio", "nChangeAmount",
    "nVolume", "nTurnover", "nAmplitude", "nHigh", "nLow", "nOpen", "nPreviousClose"]
FIELD_TYPES = ["INT PRIMARY KEY", "VARCHAR(10) UNIQUE", "VARCHAR(50)", "DECIMAL(10, 3)",
    "VARCHAR(10)", "DECIMAL(10, 3)", "VARCHAR(20)", "VARCHAR(20)",
    "VARCHAR(10)", "DECIMAL(10, 3)", "DECIMAL(10, 3)", "DECIMAL(10, 3)", "DECIMAL(10, 3)"]

def connect_db():
    """尝试连接到 MySQL 数据库实例。"""
    try:
        conn = pymysql.connect(
            host=DB_HOST,
            port=DB_PORT,
            user=DB_USER,
            password=DB_PASSWORD,
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        return conn
    except Exception as e:
        print(f"【错误】数据库连接失败: {e}。请检查配置!")
        return None

setup_database_and_table 通过 DDL定义了数据存储的 Schema。insert_data 函数负责数据的 高效写入,核心是采用 SQL 的 REPLACE INTO 语句,确保数据写入具备幂等性,即重复爬取时自动更新现有记录,保证数据准确和存储效率。

def setup_database_and_table(conn, db_name='stock_data_db', table_name='hs_a_stocks'):
    """创建指定的数据库和数据表结构。确保表结构与预设的字段设计匹配。"""
    cursor = conn.cursor()
    try:
        cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{db_name}` DEFAULT CHARACTER SET utf8mb4;")
        conn.select_db(db_name)
        columns_definition = ", ".join([
            f"`{en}` {dtype}" for en, dtype in zip(FIELD_NAMES_EN, FIELD_TYPES)
        ])
        create_table_sql = f"""
        CREATE TABLE IF NOT EXISTS `{table_name}` ({columns_definition}) 
        ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        """
        cursor.execute(create_table_sql)
        conn.commit()
        print(f"【成功】数据库 `{db_name}` 和数据表 `{table_name}` 初始化完成。")
        return True
    except Exception as e:
        print(f"【致命错误】数据库或表创建失败:{e}")
        conn.rollback()
        return False
    finally:
        cursor.close()

def insert_data(conn, data, table_name='hs_a_stocks'):
    """批量插入/更新数据到指定数据表。使用 REPLACE INTO 语句,基于 PRIMARY KEY 或 UNIQUE KEY 进行更新。"""
    if not data:
        return
    cursor = conn.cursor()
    fields = FIELD_NAMES_EN
    placeholders = ', '.join(['%s'] * len(fields))
    columns = ', '.join([f"`{f}`" for f in fields])

    insert_sql = f"REPLACE INTO `{table_name}` ({columns}) VALUES ({placeholders})"

    values_to_insert = [tuple(row) for row in data]

    try:
        cursor.executemany(insert_sql, values_to_insert)
        conn.commit()
        print(f"【数据】成功插入/更新 {len(data)} 条记录到 `{table_name}`。")
    except Exception as e:
        print(f"【错误】数据批量插入失败:{e}")
        conn.rollback()
    finally:
        cursor.close()

该模块负责启动 Headless Chrome并配置驱动。handle_advertisement 函数通过 JavaScript 注入 强制移除可能遮挡点击的 DOM 元素,增强了爬虫的抗干扰能力,是保证后续自动化操作顺利进行的关键。

def handle_advertisement(driver):
    """处理网页中可能弹出的广告窗口和遮罩层。防止 ElementClickInterceptedException 错误。"""
    # 1. 尝试关闭广告按钮
    try:
        close_button = WebDriverWait(driver, 5).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, 'div.center_box > span.close'))
        )
        close_button.click()
        print("【调试】成功关闭广告弹窗。")
        time.sleep(1)
    except (TimeoutException, NoSuchElementException, WebDriverException):
        print("【调试】未检测到广告弹窗或关闭失败,跳过。")

    # 2. 强制移除遮罩层 (使用JS是最后的办法)
    try:
        js_remove_modal = """
        var modal = document.querySelector('div[style*="position: fixed"][style*="z-index: 99998"]');
        if (modal) {
            modal.remove();
            return true;
        }
        return false;
        """
        if driver.execute_script(js_remove_modal):
            print("【调试】通过 JS 移除了遮挡点击的固定层。")
            time.sleep(0.5)
    except Exception:
        pass


def crawl_and_store():
    """A股数据采集主函数。负责 WebDriver 初始化、网页访问、分页爬取和数据存储。"""

    # 1. 建立数据库连接并创建表
    db_conn = connect_db()
    if not db_conn or not setup_database_and_table(db_conn):
        print("【终止】程序因数据库初始化问题而退出。")
        return

    # 2. 配置 Selenium WebDriver
    driver = None
    try:
        options = webdriver.ChromeOptions()
        options.add_argument('--headless') # 💡 注意:为了自动化运行,使用无头模式
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument('--incognito') # 使用隐身模式,更干净

        driver = webdriver.Chrome(options=options)
        driver.set_page_load_timeout(60)
    except Exception as e:
        print(f"【错误】WebDriver 初始化失败。请检查 Chrome/Chromedriver 版本匹配:{e}")
        db_conn.close()
        return

    print("【状态】WebDriver 启动成功,开始执行爬取任务...")
    
    # ... (后续代码从这里开始)

这是爬虫的核心执行体和控制流。while 循环实现了对目标页数的迭代,并利用 time.sleep 来应对动态数据延迟加载。数据采集部分通过 XPath 精准定位表格结构数据。翻页时,强制使用 JavaScript 注入实现点击操作,保证了分页自动化流程的稳定性和健壮性。最后,将所有采集到的数据进行统一存储并安全关闭所有连接。

def crawl_and_store():
    # ... (前面的初始化代码)

    try:
        driver.get(URL)
        print(f"【访问】正在加载目标网址: {URL}")

        handle_advertisement(driver)

        page = 1
        total_pages = 5
        all_data = []
        ROWS_PER_PAGE = 20

        while page <= total_pages:
            print(f"\n--- 🌐 开始处理第 {page} 页 (目标共 {total_pages} 页) ---")

            print("【等待】暂停 5 秒,等待股票数据表格完全渲染...")
            time.sleep(5)

            current_page_data = []

            try:
                # 1. 使用 XPath 定位数据行
                xpath_selector = f"//div[@class='{TABLE_CONTAINER_CLASS}']//tbody/tr"
                rows = driver.find_elements(By.XPATH, xpath_selector)

                if not rows:
                    raise NoSuchElementException("错误:找不到数据行,请检查表格 CSS 或页面加载状态。")

                print(f"【采集】本页成功找到 {len(rows)} 条股票记录。")

                for i, row in enumerate(rows):
                    cols = row.find_elements(By.TAG_NAME, "td")

                    if len(cols) >= 14:
                        # 字段提取和格式化
                        data = [
                            (page - 1) * ROWS_PER_PAGE + i + 1,
                            cols[1].text.strip(), cols[2].text.strip(), cols[4].text.strip(),
                            cols[5].text.strip(), cols[6].text.strip(), cols[7].text.strip(),
                            cols[8].text.strip(), cols[9].text.strip(), cols[10].text.strip(),
                            cols[11].text.strip(), cols[12].text.strip(), cols[13].text.strip()
                        ]
                        current_page_data.append(data)

                all_data.extend(current_page_data)

            except (NoSuchElementException, TimeoutException) as e:
                print(f"【错误】第 {page} 页数据采集中断:{e}")
                break

            # ... (分页信息获取逻辑)

            # 执行翻页操作
            if page < total_pages:
                try:
                    next_page_button = driver.find_element(
                        By.CSS_SELECTOR,
                        'div.qtpager > a[title="下一页"]'
                    )
                    # 💡 强制使用 JS 点击,避免被遮挡 (防止 ElementClickInterceptedException)
                    driver.execute_script("arguments[0].click();", next_page_button)

                    print(f"【操作】成功点击下一页 ({page + 1}/{total_pages})。")
                    page += 1
                    time.sleep(3)  # 留出充足时间等待新页面数据加载
                except NoSuchElementException:
                    print("【结束】未找到下一页按钮,爬取流程提前结束。")
                    break
            else:
                break

        # 4. 将所有数据插入数据库
        print(f"\n--- 【完成】数据采集结束,共获取 {len(all_data)} 条记录 ---")
        insert_data(db_conn, all_data)

    except Exception as e:
        print(f"【致命错误】爬虫运行时发生未捕获的异常:{e}")

    finally:
        # 5. 关闭连接
        if driver:
            driver.quit()
        db_conn.close()
        print("【状态】WebDriver 和数据库连接已关闭。程序安全退出。")

if __name__ == '__main__':
    crawl_and_store()

运行结果

image
沪深 A 股
image
上证 A 股
image
深证 A 股
image

心得体会

本次实践在技术和专业流程上获得了显著提升:

  1. 动态采集与流程稳定
    我精通了 Selenium WebDriver 处理复杂 动态网页 的方法。通过 JavaScript 注入,解决了 DOM 遮罩和 强制翻页 的难题。

  2. 数据持久化与效率优化
    在数据存储端,利用 REPLACE INTO 语句和 executemany() 实现了对股票数据的 批量、高效更新,确保了数据写入的准确性和高性能。

  3. 技术规范与模块化
    项目强化了 XPath 精准定位技能。同时,通过 Headless Mode 和 try...except 机制,项目展现出对健壮的异常处理 的专业认知。

作业二

熟练掌握 Selenium 查找 HTML 元素、实现用户模拟登录、爬取 Ajax 网页数据、等待 HTML 元素等内容。
使用 Selenium 框架+MySQL 爬取中国 mooc 网课程资源信息(课程号、课程名称、学校名称、主讲教师、团队成员、参加人数、课程进度、课程简介)
候选网站:中国 mooc 网:https://www.icourse163.org

代码思路讲解

数据库连接和表结构创建两个函数。connect_db通过 pymysql 建立数据库连接,失败时返回 None 并提示错误;setup_database_and_table创建指定数据库和数据表,按需生成表结构语句,支持事务回滚,确保操作安全性。

def connect_db():
    """
    建立与MySQL数据库的连接
    返回值:
        pymysql.connect对象 - 数据库连接实例(连接失败返回None)
    """
    try:
        conn = pymysql.connect(
            host=DB_HOST,
            port=DB_PORT,
            user=DB_USER,
            password=DB_PASSWORD,
            charset='utf8mb4',
            cursorclass=pymysql.cursors.DictCursor
        )
        return conn
    except Exception as e:
        print(f"数据库连接失败: {e}")
        return None

def setup_database_and_table(conn, db_name=DB_NAME, table_name=TABLE_NAME):
    """
    创建目标数据库及数据表(不存在时创建)
    参数:
        conn: 数据库连接对象
        db_name: 目标数据库名称
        table_name: 目标数据表名称
    返回值:
        bool - 创建成功返回True,失败返回False
    """
    cursor = conn.cursor()
    try:
        cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{db_name}` DEFAULT CHARACTER SET utf8mb4;")
        conn.select_db(db_name)
        columns_definition = ", ".join([
            f"`{en}` {dtype}" for en, dtype in zip(FIELD_NAMES_EN, FIELD_TYPES)
        ])
        create_table_sql = f"""
        CREATE TABLE IF NOT EXISTS `{table_name}` ({columns_definition}) 
        ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        """
        cursor.execute(create_table_sql)
        conn.commit()
        print(f"数据库 `{db_name}` 和数据表 `{table_name}` 创建/验证成功。")
        return True
    except Exception as e:
        print(f"数据库/数据表创建失败: {e}")
        conn.rollback()
        return False
    finally:
        cursor.close()

该函数实现 MOOC 平台模拟登录,先访问登录页并切换到登录 iframe,输入账号密码后提交,等待用户手动完成滑块验证,最后检测个人中心入口确认登录状态,超时或异常时返回 False 并提示具体错误。

def login(driver, username, password):
    """
    模拟中国大学MOOC平台登录流程
    参数:
        driver: Selenium WebDriver实例
        username: 登录账号(手机号)
        password: 登录密码
    返回值:
        bool - 登录成功返回True,失败返回False
    """
    print("\n--- 开始执行平台登录操作 ---")
    driver.get(URL_LOGIN)
    print(f"成功访问登录页面: {URL_LOGIN}")
    time.sleep(3)
    try:
        iframe_container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.ID, 'j-ursContainer-0'))
        )
        iframe_element = iframe_container.find_element(By.TAG_NAME, 'iframe')
        driver.switch_to.frame(iframe_element)
        print("成功切换到登录Iframe容器。")
        time.sleep(2)
    except TimeoutException:
        print("登录Iframe容器定位失败或切换超时。")
        return False
    try:
        wait_iframe = WebDriverWait(driver, 20)
        phone_input = wait_iframe.until(EC.visibility_of_element_located((By.ID, 'phoneipt')))
        phone_input.send_keys(username)
        password_input = wait_iframe.until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'input[type="password"][placeholder="请输入密码"]')))
        password_input.send_keys(password)
        submit_btn = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.ID, 'submitBtn')))
        submit_btn.click()
        print("登录按钮点击成功。")
        driver.switch_to.default_content()
        print("\n=============================================")
        print("检测到滑块验证,请手动在浏览器中完成验证操作!")
        print("完成验证后,按下回车键继续程序执行!")
        print("=============================================\n")
        input("等待用户完成滑块验证,按回车键继续...")
        wait = WebDriverWait(driver, 30)
        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a[href*="/home.htm"]')))
        print("登录验证成功,已识别到个人中心入口。")
        return True
    except TimeoutException:
        print("登录超时(30秒):未检测到登录成功标识,请确认滑块验证已完成。")
        return False
    except Exception as e:
        print(f"登录流程执行失败: {e.__class__.__name__}: {e}")
        driver.switch_to.default_content()
        return False

该函数采集课程详情页的教师和简介信息,先滚动页面触发懒加载,尝试切换内容 iframe,等待页面加载后定位元素提取信息,失败时标注 N/A,最后切回主文档,返回采集结果并输出状态提示。

def crawl_course_details(driver):
    """
    从课程详情页采集教师信息及课程简介
    参数:
        driver: Selenium WebDriver实例
    返回值:
        tuple - (教师姓名, 课程简介)
    """
    teacher_names = "N/A"
    course_brief = "N/A"
    iframe_switched = False
    print("    滚动页面以触发内容懒加载...")
    driver.execute_script("window.scrollTo(0, 2500);")
    time.sleep(2)
    try:
        iframe_elements = driver.find_elements(By.TAG_NAME, 'iframe')
        if iframe_elements:
            driver.switch_to.frame(iframe_elements[0])
            iframe_switched = True
            print("    成功切换到内容Iframe容器。")
    except Exception as e:
        print(f"    切换Iframe失败: {e.__class__.__name__}")
    print("    等待页面内容加载完成(15秒)...")
    time.sleep(15)
    try:
        teacher_elements = driver.find_elements(By.CSS_SELECTOR, '.m-teachers_teacher-list h3.f-fc3')
        teacher_names = "、".join([t.text.strip() for t in teacher_elements if t.text.strip()])
        if not teacher_names:
            teacher_names = "N/A (无教师信息)"
    except Exception:
        teacher_names = "N/A (教师信息定位失败)"
    try:
        brief_element = driver.find_element(By.ID, 'j-rectxt2')
        course_brief = brief_element.text.strip()
        if course_brief:
            course_brief = course_brief[:250]
        else:
            course_brief = "N/A (无课程简介)"
    except Exception:
        course_brief = "N/A (课程简介定位失败)"
    if iframe_switched:
        driver.switch_to.default_content()
        print("    已切换回主文档。")
    if teacher_names.startswith("N/A") and course_brief.startswith("N/A"):
        print("    课程详情采集失败。")
    else:
        print("    课程详情采集完成。")
    print(f"    教师信息: {teacher_names[:20]}...; 课程简介: {course_brief[:20]}...")
    return teacher_names, course_brief

主函数先初始化数据库连接和表结构,再配置 Chrome 选项启动浏览器,完成 MOOC 登录后导航至个人课程中心。过程中捕获初始化、登录、导航异常,最终释放浏览器和数据库连接,保障资源正常回收。

def crawl_and_store():
    """
    爬虫主函数:完成数据库初始化、平台登录、课程数据采集、数据入库全流程
    """
    db_conn = connect_db()
    if not db_conn or not setup_database_and_table(db_conn, table_name=TABLE_NAME):
        print("数据库初始化失败,程序终止。")
        return
    driver = None
    try:
        options = webdriver.ChromeOptions()
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument("user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
        driver = webdriver.Chrome(options=options)
        driver.set_page_load_timeout(90)
    except Exception as e:
        print(f"WebDriver初始化失败: {e}")
        db_conn.close()
        return
    print("浏览器启动成功,开始执行数据采集流程...")
    try:
        if not login(driver, USERNAME, PASSWORD):
            print("登录操作失败,程序终止。")
            return
        print("开始导航至个人课程中心...")
        try:
            personal_center_link = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//a[text()="个人中心"]')))
            personal_center_link.click()
            print("成功点击个人中心链接。")
            time.sleep(2)
            course_list_tab = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//a[contains(@href, "/home/course")]')))
            course_list_tab.click()
            print("成功点击我的学习链接。")
            time.sleep(3)
            print(f"当前页面地址: {driver.current_url}")
        except TimeoutException:
            print("课程中心导航失败,程序终止。")
            return
        # 后续课程采集逻辑省略(核心初始化/登录/导航部分)
    except Exception as e:
        print(f"程序执行异常: {e}")
    finally:
        if driver:
            driver.quit()
        db_conn.close()
        print("程序执行完毕,已关闭浏览器及数据库连接。")

运行结果

image
image
在完成任务的过程中,我先通过 Selenium 模拟登录(处理 iframe 和滑块验证),导航至课程中心;解析课程基础信息,进入详情页采集教师、简介数据;最后用 pymysql 完成数据库初始化与数据插入,全程加异常处理保障稳定。
本次实践掌握了 Selenium 爬虫核心技能,熟悉 MySQL 表设计与批量插入。学会解决动态页面懒加载、元素失效问题,提升了异常处理能力,理解了爬虫开发中稳定性与数据准确性的关键意义。

任务三

掌握大数据相关服务,熟悉 Xshell 的使用
完成文档 华为云_大数据实时分析处理实验手册-Flume 日志采集实验(部
分)v2.docx 中的任务,即为下面 5 个任务,具体操作见文档。
任务一:开通 MapReduce 服务
image
image
任务二:Python 脚本生成测试数据
image
image
image
image

任务三、任务四:配置 Kafka并安装 Flume 客户端
image
image
image
image
image

心得体会

本次实验使我系统实践了数据生成、传输至采集的全链路技术体系。借助 Xshell 高效操控远程服务器,既强化了 Linux 命令的应用熟练度,也深化了对大数据组件协同运行逻辑的认知。后续可探索 Flume 拦截器的数据预处理能力,以及 Kafka 在实时流处理场景中的深度应用。

posted @ 2025-12-09 23:09  102302131陈宇新  阅读(3)  评论(0)    收藏  举报