网络爬虫记录

前置知识

网站的加载形式有两种:静态加载和动态加载

静态加载

在静态加载中,当你访问一个网页时,服务器会发送一个完整的HTML页面。

所有的内容,包括文本、图片和链接,都嵌入在这个HTML文档中。

CSS和JavaScript通常作为外部文件加载,但它们主要用于增强页面的外观和感觉,而不是改变内容。

对爬虫的影响:

易于爬取:静态页面可以直接通过HTTP请求获取,然后使用HTML解析器(如BeautifulSoup)提取所需信息。

无需执行JavaScript:不需要处理JavaScript生成的内容。

动态加载

动态加载的网页通常使用Ajax(Asynchronous JavaScript and XML)或其他JavaScript框架来异步加载数据。

当你访问这样的网页时,初始的HTML文档可能不包含所有内容。随后,JavaScript会被执行来加载更多数据。

这些数据可能来自服务器的额外HTTP请求,通常返回JSON或XML格式的数据。

对爬虫的影响:

更复杂的爬取过程:由于内容是动态加载的,传统的HTTP请求和HTML解析可能无法获取所有数据。

需要模拟浏览器或分析JavaScript:可能需要使用如Selenium之类的工具来模拟浏览器行为,或分析JavaScript代码和网络请求来直接获取数据。

可能涉及到更多的反爬机制:动态加载的网站可能有更复杂的反爬虫策略。

Selenium库

在刷新网易云音乐的一首歌曲下面的评论的时候,发现浏览器的URL都没有发生变化,应该是采用Ajax刷新了网页的部分内容。

对于这样网站的爬虫有两个思路:

  1. 在浏览器的控制台上刷新时候查看“网络”部分,分析发送的请求,在相应的进行模拟
  2. 使用selenium工具进行模拟

本次任务采用selenium进行模拟进行爬取评论。

然后会得到一堆.txt文件,用python或者shell脚本把他们合在一起,接下来进行数据分析

代码附录

代码1 网易云音乐评论网络爬虫

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep

# 创建ChromeOptions对象并启用无头模式
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
driver = webdriver.Chrome(options=chrome_options)

# 打开网站
driver.get('https://music.163.com/#/song?id=1392155391')
# 设定起始页号
page_number = 1

# 定位元素框架
comment_frame = driver.switch_to.frame('g_iframe')

while page_number<260:

    # 滚动到页面底部,确保所有的评论都被加载
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    # 等待一段时间,让评论加载
    sleep(2)
    
    # 定位评论和时间
    comments = driver.find_elements(By.CSS_SELECTOR, ".cnt.f-brk")
    times = driver.find_elements(By.CSS_SELECTOR, ".time.s-fc4")

    # 写入到文件
    with open(f'comments_page_{page_number}.txt', 'w', encoding='utf-8') as f:
        for comment in comments:
            # 提取评论用户名、文本、时间
            text = comment.text
            time = times[comments.index(comment)].text
            # 在控制台打印评论
            print(f'page_{page_number}:'+time+' '+text)
            # 写入文件
            f.write(time+' '+text + '\n')

    # 翻到下一页
    try:
        next_page_button = driver.find_element(By.CSS_SELECTOR, '.zbtn.znxt')
        next_page_button.click()
        page_number += 1
    except Exception as e:
        # 如果找不到 "下一页" 按钮,说明已经到达最后一页,跳出循环
        break
        
        # 事实上,他会一直在最后一个页面循环输出相同的结果
        # 解决方法1:手动终止程序,删除多余的txt
        # 解决方法2:写成for循环,硬编码最大页数
        # !!这里采用了硬编码最大页数的方法
    
# 关闭浏览器和结束 WebDriver 会话
driver.quit()

代码2 携程旅行网站爬虫

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
from bs4 import BeautifulSoup

# 创建ChromeOptions对象并启用无头模式
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
driver = webdriver.Chrome(options=chrome_options)

# 打开网站
driver.get('https://you.ctrip.com/sight/pancounty2369/134972.html')
# 设定起始页号
page_number = 1

# 点击按照时间排序
element = driver.find_element(By.CSS_SELECTOR,'.sortTag')
element.click()

while page_number<141:

    # 滚动到页面底部,确保所有的评论都被加载
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    # 等待一段时间,让评论加载
    sleep(2)
    
    # 定位评论和IP地址
    comments = driver.find_elements(By.CSS_SELECTOR, ".commentDetail")
    times= driver.find_elements(By.CSS_SELECTOR, ".commentTime")
    ipContent = driver.find_elements(By.CSS_SELECTOR, ".ipContent")

    # 写入到文件
    with open(f'comments_page_{page_number}.txt', 'w', encoding='utf-8') as f:
        for comment, time in zip(comments, times):
            # 提取评论用户名、文本、时间
            text = comment.text
            ip = ipContent[comments.index(comment)].text

            # 获取 WebElement 对象的 outerHTML 属性
            html = time.get_attribute('outerHTML')

            soup = BeautifulSoup(html, 'html.parser')

            # 找到 div 和 span 元素
            div = soup.find('div', class_='commentTime')
            span = div.find('span', class_='ipContent')

            # 获取 div 和 span 的文本
            div_text = div.get_text(strip=True)
            span_text = span.get_text(strip=True)

            # 从 div 的文本中移除 span 的文本
            time_text = div_text.replace(span_text, '')

            # 在控制台打印评论
            print(f'page_{page_number}:' + ip + '   ' + time_text + '    ' + text)

            # 写入文件
            f.write(ip + '   ' + time_text + '    ' + text + '\n')

    # 翻到下一页
    try:
        next_page_button = driver.find_element(By.CSS_SELECTOR, '.ant-pagination-next')
        # next_page_button.click()
        driver.execute_script("arguments[0].click();", next_page_button)
        page_number += 1
    except Exception as e:
        # 如果找不到 "下一页" 按钮,说明已经到达最后一页,跳出循环
        print("没有找到下一页")
        print(e)
        break
        
        # 事实上,他会一直在最后一个页面循环输出相同的结果
        # 解决方法1:手动终止程序,删除多余的txt
        # 解决方法2:写成for循环,硬编码最大页数
        # !!这里采用了硬编码最大页数的方法
    
# 关闭浏览器和结束 WebDriver 会话
driver.quit()

代码3 merge_all_txt.py

import glob

# 找到所有的 'comments_page_{page_number}.txt' 文件
filenames = glob.glob('comments_page_*.txt')

# 打开 'comments.txt' 文件
with open('comments.txt', 'w', encoding='utf-8') as outfile:
    for fname in filenames:
        # 打开每个 'comments_page_{page_number}.txt' 文件
        with open(fname, 'r', encoding='utf-8') as infile:
            # 将文件的内容写入到 'comments.txt' 文件中
            for line in infile:
                outfile.write(line)