网络爬虫记录
前置知识
网站的加载形式有两种:静态加载和动态加载
静态加载
在静态加载中,当你访问一个网页时,服务器会发送一个完整的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刷新了网页的部分内容。
对于这样网站的爬虫有两个思路:
- 在浏览器的控制台上刷新时候查看“网络”部分,分析发送的请求,在相应的进行模拟
- 使用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)