Bootstrap

Python爬虫(Scrapy、Selenium、BeautifulSoup、Jupyter的使用)

一、常见的Python爬虫库

1. Requests:处理Http请求。

2. lxml:HTML和XML文件解。

3. BeautifulSoup:网络抓取框架,用于解析和提取HTML和XML数据,通常用于小数据量的处理,不支持异步操作,通常搭配lxml使用。

4. Scrapy:比较强大的爬虫框架,适用于复杂大型任务的爬取。

5. Selenium:模拟用户访问浏览器操作,适合处理JS渲染的网页。

6. re:正则表达式。

二、爬虫示例 1

示例描述: 爬取自己的csdn博客,统计每篇博客的访问量,制作一个柱状图,以访问量从大到小的方式显示。

1. 从“个人主页”爬取所有所有文章的链接

1.1 查看爬取规则

        打开个人主页,右键->检查:可以看到每篇文章的链接挂在哪个标签的哪个属性下( <article>标签下的<a>标签中的href属性值即为每篇文章的链接 )

1.2 提取网页中的所有文章ip

from bs4 import BeautifulSoup  #pip3 install beautifulsoup4
from urllib.request import urlopen

homePage_url="https://blog.csdn.net/beautiful77moon?type=blog"  #你的csdn个人主页链接
homePage_html=urlopen(homePage_url).read().decode('utf-8')
soup=BeautifulSoup(homePage_html,features='lxml')

#1.查找所有的<article>标签
li_articles=soup.find_all('article')

#2.取出所有<article>标签下<a>中的href属性值
article_urls=[]
for item in li_articles:
    link=item.find_all('a')
    article_urls.append(link[0]['href'])
    print(link[0]['href'])

        当页面内容过多时,需要下拉"加载",才能显示所有内容,所以需要一个工具模拟浏览器行为,自动滚动页面以加载更多内容。urllib无法处理这种情况,所以一般不建议使用 urllib。

1.3 使用selenium模拟浏览器。

 1.3.1  下载浏览器驱动(以Edge为例)

1. 查看自己的浏览器版本(点击浏览器右上角的三个点->设置->关于 Microsoft Edge)

2. 下载对应版本的驱动:Microsoft Edge WebDriver | Microsoft Edge Developer

3. 解压到一个目录下(这个目录后续会用到)

1.3.2  下载关键的依赖包

1. 浏览器模拟器selenium:pip install selenium --index-url https://pypi.tuna.tsinghua.edu.cn/simple

2. 处理网页的beautifulsoup:pip install beautifulsoup4 --index-url https://pypi.tuna.tsinghua.edu.cn/simple

1.3.3 代码

1. 模拟浏览器,实现“滑动鼠标”下拉页面以加载更多数据的行为

2. 从个人主页提取所有文章的url并打印

from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.edge.options import Options
from bs4 import BeautifulSoup
import time

# 设置 Edge驱动 的路径
edge_driver_path = 'E:\SoftWare_work\download\edgedriver_win64\msedgedriver.exe'  # 替换为你本地的 EdgeDriver 路径

# 配置 Edge浏览器选项
edge_options = Options()
edge_options.add_argument("--headless")  # # 无头模式。不可视化浏览器页面。如果注释掉这行,每次运行代码都会打开浏览器页面。

# 启动浏览器
service = Service(edge_driver_path)
driver = webdriver.Edge(service=service, options=edge_options)

# 打开网页
homePage_url = "https://blog.csdn.net/beautiful77moon?type=blog"
driver.get(homePage_url)

# 滚动页面以加载更多内容
# driver.execute_script("return window.pageYOffset + window.innerHeight") 用于获取当前视口底部相较于页面顶部位置的 JavaScript 代码
# window.pageYOffset:当前视口顶部相对于页面顶部的垂直滚动距离。表示页面的顶部已经滚动了多少像素     假设为1200px
# window.innerHeight:浏览器视口的高度(即当前显示区域的高度) 假设为800px
# last_height = window.pageYOffset + window.innerHeight = 2000px
last_height = driver.execute_script("return window.pageYOffset + window.innerHeight")
while True:
    #driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") #使用 JavaScript 滚动到页面底部以加载更多数据
    #模拟鼠标滚动
    #第一个参数表示水平方向不滚动
    #第二个参数表示垂直方向上滚动1000像素
    driver.execute_script(f"window.scrollBy(0, 1000);")
    time.sleep(5) #等待页面加载
    new_height = driver.execute_script("return window.pageYOffset + window.innerHeight")#driver.execute_script("return document.body.scrollHeight") #获取当前页面的高度,检测是否已加载更多内容
    if new_height == last_height:
        break
    last_height = new_height

# 获取页面源代码
page_source = driver.page_source

# 关闭浏览器
driver.quit()

# 解析页面内容
soup = BeautifulSoup(page_source, 'lxml')

# 查找所有的<article>标签
li_articles = soup.find_all('article')

# 取出所有<article>标签下<a>中的href属性值
article_urls = []
for item in li_articles:
    links = item.find_all('a')
    if links:
        article_urls.append(links[0].get('href'))
        print(links[0].get('href'))
print(len(article_urls))  #116

2. 统计每篇文章的访问量

注:该内容可以直接从首页爬取,这里是为了演示爬虫爬取数据的一种逻辑。

2.1 查看爬取规则

参考 1.1

2.2 爬取数据   

注意:1.3.3 代码中的 driver.quit() 需要注释掉,否则不能正常浏览其他网页,会报错

import re
#分析每篇文章的访问量,收藏量,点赞数
class Article_Detail:
    def __init__(self,article_name="",read="",collect="",like=""):
        self.article_name=article_name #文章名字
        self.read=read #阅读量
        self.collect=collect #收藏量
        self.like=like #点赞量
articles_details=[]
for item in article_urls:
    driver.get(item)
    page_source=driver.page_source
    #driver.quit()
    soup = BeautifulSoup(page_source,'lxml')
    # 阅读量、收藏量、点赞数所在标签过滤
    bar_content=Article_Detail()
    get_name=(soup.find('h1',attrs='title-article',id='articleContentId')).get_text()
    get_read=(soup.find('span','read-count')).get_text(strip=True) #strip=True去除前后空格
    get_collection=(soup.find('span','get-collection')).get_text(strip=True)
    get_like=(soup.find('span',id='blog-digg-num')).get_text(strip=True)

    #统计文章的阅读量、收藏量、点赞数
    bar_content.article_name=get_name

    match=re.search(r'\d+',get_read) #使用正则表达式匹配字符串中的一个或多个数字
    bar_content.read= match.group() if match else 0

    match = re.search(r'\d+', get_collection)  # 使用正则表达式匹配字符串中的一个或多个数字
    bar_content.collect = match.group() if match else 0

    match = re.search(r'\d+', get_like)  # 使用正则表达式匹配字符串中的一个或多个数字
    bar_content.like = match.group() if match else 0

    articles_details.append(bar_content)

for item in articles_details:
    print(item.article_name+":阅读量["+item.read+"],收藏量["+item.collect+"],点赞数["+item.like+"]")

 运行结果:

三、爬虫框架Scrapy

使用Scrapy框架实现"示例1"中的功能,并将爬取的结果进行保存。

1. Scrapy框架与BeautifulSoup框架的区别:

Scrapy教程

[功能方面]

  • Scrapy:
    • 数据类型:可以处理各种类型的数据。
    • 功能方面:包括请求管理、数据解析/提取、数据存储(JSON、CSV、XML)等。
    • 请求方式:使用异步 I/O,使其能够高效地处理大量请求。
    • 支持的特性:支持中间件、管道、爬虫调度等功能,适合大规模的抓取项目。
    •  自动处理:能够自动处理重定向、用户代理、更换IP等。
  • BeautifulSoup:       
    • 数据类型:是一个用于解析 HTML 和 XML 的库,主要用于提取网页内容。
    • 功能方面:只解析和提取数据,不包括请求管理和数据存储。
    • 请求方式:同步I/O操作,通常与requests等库配合使用。

[性能方面]

  • Scrapy:
    • 性能高:因其异步I/O和内置的请求调度功能,适合大规模抓取。
    • 扩展性强:可以通过中间件和扩展进行功能扩展,如处理JavaScript渲染内容等。
  • BeautifulSoup:
    • 性能较低:通常用于小规模数据抓取,不支持异步操作。
    • 扩展性有限,主要集中与数据解析,需要与其他工具组合使用。

[适用场景]

  • Scrapy:
    • 大规模爬取:适合需要高效处理大量数据的项目。
    • 复杂任务抓取:适用于抓取并处理多个页面、分页、异步请求等复杂任务。
  • BeautifulSoup:
    • 小型抓取:适合简单的数据提取任务。
    • 数据解析:已经知道了html内容,只需要解析和提取数据时。

2.  Scrapy框架使用步骤

1. 下载相应的依赖包:pip install scrapy

2. 命令行创建一个爬虫项目:scrapy startproject csdn_blog

2. 创建爬虫:

        ①进入爬虫目录:cd csdn_blog

        ② 执行命令:scrapy genspider csdn_spider https://blog.csdn.net/beautiful77moon?type=blog

4. 打开items.py定义item容器

import scrapy

class CsdnBlogItem(scrapy.Item):
    # define the fields for your item here like:
    title=scrapy.Field()
    read=scrapy.Field()
    collect=scrapy.Field()
    like=scrapy.Field()

5. 进入 csdn_spider.py编写爬虫代码(不包含模拟浏览器的功能)

注:文章的数据内容可以直接从首页爬取,为了演示爬虫爬取数据的逻辑,这里对抓取的所有ip都进行了爬取。 实际应用中,怎么简单怎么处理。

import re
import scrapy

class CsdnSpiderSpider(scrapy.Spider):
    name = "csdn_spider"
    allowed_domains = ["blog.csdn.net"]
    start_urls = ["https://blog.csdn.net/beautiful77moon?type=blog"]

    def parse(self, response):
        #提取文章链接
        article_urls=response.css('article a::attr(href)').getall()  # ::attr(href) 提取href属性的值
        article_urls=[url for url in article_urls if url.startswith("https://")]

        #分析所有链接
        for url in article_urls:

            yield scrapy.Request(url,callback=self.parse_article)

    #提取每篇文章的数据
    def parse_article(self,response):
        title=response.css('h1.title-article::text').get(default='N/A').strip() #::text 提取文本内容
        read=self.extract_number(response.css('span.read-count::text').get(default='0').strip()) #
        collect=self.extract_number(response.css('span.get-collection::text').get(default='0').strip()) # '.':class
        like=self.extract_number(response.css('span#blog-digg-num::text').get(default='0').strip()) # '#':id

        yield {
            'title':title,
            'read' : read,
            'collect' : collect,
            'like' : like
        }
    
    #正则化
    def extract_number(self, text):
        match = re.search(r"\d+(\.\d+)?k?", text) #对于1200这种数据会显示为1.2k
        number=match.group() if match else '0'
        if 'k' in number:
            number=number.replace('k','')
            number=float(number)*1000
        else:
            number=int(number)
        return int(number)

6. 运行: scrapy crawl csdn_spider -o articles.json (将爬取的内容输出到articles.json)

注意: 运行时会报错:Forbidden by robots.txt: <GET https://blog.csdn.net/beautiful77moon?type=blog>

网站的robots.txt文件阻止了 Scrapy 访问该页面,可以修改settings.py中的ROBOTSTXT_OBEY = False配置:来忽略robots.txt规则。(谨慎使用)

        8. 运行结果如下:

四、使用Jupyter对结果进行可视化

1. 安装所需要的包 

 pip install notebook --index-url https://pypi.tuna.tsinghua.edu.cn/simple

 pip install pandas --index-url https://pypi.tuna.tsinghua.edu.cn/simple

 pip install matplotlib --index-url https://pypi.tuna.tsinghua.edu.cn/simple

 pip install seaborn --index-url https://pypi.tuna.tsinghua.edu.cn/simple

2. 启用jupyter notebook 

1. 在pycharm中运行命令,打开一个网页: jupyter notebook 

2. 新建notebook

3. 运行代码

 

3. 对爬取的数据可视化 

绘制直方图和曲线图显示爬取的所有数据:

1. X轴:每篇文章的标题

2. Y轴:直方图的Y轴数据为所有数据的和,三条曲线图的Y轴数据分别为博客的阅读量、收藏量和点赞量。


import pandas as pd
import matplotlib.pyplot as plt
import json

# 读取JSON文件并加载数据
with open('article.json', 'r', encoding='utf-8') as file:
    data = json.load(file)

# 将数据转换为DataFrame
df = pd.DataFrame(data)

# 检查是否有缺失值并处理(例如填充缺失值或删除含缺失值的行)
df = df.dropna(subset=['read', 'collect', 'like'])

X=df["title"]
Y_read=df["read"]
Y_collect=df["collect"]
Y_like=df["like"]
Y_sum=Y_read+Y_collect+Y_like

# 配置字体 不配置字体可能会出现中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体
plt.rcParams['axes.unicode_minus'] = False  # 正确显示负号

plt.figure()
plt.bar(X,Y_sum,facecolor='purple',edgecolor='white',label='总数') #直方图
for x,y in zip(X,Y_sum):
    plt.text(x,y+50,y,ha="center",va="top")

plt.plot(X,Y_read,color='red',label='阅读量')
for x,y in zip(X,Y_read):
    plt.text(x,y+50,y,ha="center",va="top",color='red')

plt.plot(X,Y_collect,color='green',label='收藏量')
for x,y in zip(X,Y_collect):
    plt.text(x,y+50,y,ha="center",va="top",color='green')

plt.plot(X,Y_like,color='blue',label='点赞量')
for x,y in zip(X,Y_like):
    plt.text(x,y+50,y,ha="center",va="top",color='blue')

#添加图例
plt.legend()

#旋转x轴标签
plt.xticks(rotation=45,ha='right')

# 显示图形
plt.show()

   运行结果: 

;