简单爬虫案例

news2024/9/21 20:44:50

准备工作:

1. 安装好python3 最低为3.6以上, 并成功运行pyhthon3 程序

2. 了解python 多进程原理

3. 了解 python  HTTP 请求库 requests 的基本使用

4. 了解正则表达式的用法和python 中 re 库的基本使用

爬取目标

目标网站: https://ssr1.scrape.center/

一个静态网站

目标:

利用 requests 爬取这个站点每一页的电影列表, 顺着列表再爬取每个电影的详情页

利用正则表达式提取每部电影的名称, 封面, 类别, 上映时间, 评分, 剧情简介等内容

把以上爬取的内容存储为JSON文本文件

使用多进程实现爬取加速

爬取列表页

引入必要的库:

import requests
import logging
import re
from urllib.parse import urljoin
 

 requests 库用来抓取页面, logging 库用来输出信息, re 库用来实现正则表达式解析,urljoin用来做URL拼接

设置一些基础变量:

logging.basicConfig(level=logging.INFO, format='%(asctime)s- %(levelname)s: %(message)s')
BASE_URL = 'https://ssr1.scrape.center'
TOTAL_PAGE =10

接着定义了日志输出级别和输出格式, 以及BASE_URL 为当前站点的根URL

TOTAL_PAGE为需要爬取的总页码量

def scrape_page(url):
    logging.info('scraping %s....', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        logging.error('get invalid staus code %s while scraping %s', response.status_code, url)
    except requests.RequestException:
        logging.error('error occurred while scraping %s', url, exc_info=True)

考虑到不仅要爬取列表页面,也要爬取详情页, 所以这里我们定义了一个比较通用的爬取页面的方法, 叫做 scrape_page, 它接收一个参数 url , 返回页面的 HTML  代码,。 首先判断状态码是否是200, 如果是就直接返回页面的HTML 代码, 如果不是,则输出错误日志。 另外这里实现了 requests 的异常处理, 如果出现爬取异常, 就输出对应的错误信息。 我们将logging 中的 error 方法里的 exc_info 设置为 True 可以打印出 Traceback 错误堆栈信息

在 scrape_page 的基础上, 我们来定义列表页的爬取方法:

def scrape_index(page):
    index_url = f'{BASE_URL}/page/{page}'
    return scrape_page(index_url)

方法名: scrape_index  这个方法接收一个 page 参数, 即列表页的页码, 我们在方法里面实现页面URL 的拼接, 然后调用 scrape_page 的方法即可, 这样就就可以获取HTML代码了

下一步就是获取列表页, 并得到每部电影详情的URL 

def parse_index(html):
    pattern = re.compile('<a.*?href="(.*?)".*?class="name">')
    items = re.findall(pattern, html)
    if not items:
        return []
    for item in items:
        detail_url = urljoin(BASE_URL, item)
        logging.info('get detail url %s', detail_url)
        yield detail_url

在这里定义了 parse_index 方法, 它接收一个参数 html , 即列表页的代码。

在这个方法里 , 我们首先 定义了一个提取标题超链接href 属性的正则表达式, 

<a.*?href="(.*?)".*?class="name">

我们使用非贪婪通用匹配符  .*?  来匹配任意字符, 同时在 href 属性的引号之间使用了分组匹配 (.*?)  正则表达式, 这样我们就能在匹配结果中获取到 href 里面的值了。 正则表达式后紧跟着

class="name"  用来表示这个 <a> 节点 式代表电影名称的节点

然后使用 re 库中的 findall 来提取所有 href 中的值, 第一个参数是 pattern 对象, 第二个参数传入html , 这样findall 就能搜素所有 html中与该正则表达式相匹配的内容, 之后把匹配结果返回。 并赋值为 items 如果items 为空就直接返回空列表, 如果不为空 那么直接遍历处理即可,遍历items 得到的 item 就是类似 /detail/1 这样的结果, 由于这并不是一个完整的URL, 所以需要借助 urljoin 把 BASE_URL  和 href 拼接到一起, 获得详情页的完整 URL , 得到的结果就是类似 https://ssr1.scrape.conter/detail/1 这样完整URL, 最后 yield 返回即可

现在我们调用 parse_index 方法 往其中传入列表页的 HTML 代码, 就可以获取所有电影的详情页的 URL 了

 def main():
    for page in range(1, TOTAL_PAGE + 1):
        index_html = scrape_index(page)
        detail_urls = parse_index(index_html)
        logging.info('detail urls %s', list(detail_urls))
if __name__ == '__main__':
    main()

这里定义了 main 方法, 以完成所有上面方法的调用, main 方法中首先调用 range 方法遍历 了所有页码, 得到的 page 就是 1-10 接着把page 变量传给scrape_index 方法, 得到列表页 HTML 把得到的 HTML 赋值给 index_html , 接着将 index_html 传给 parse_index 方法, 得到列表页所有电影详情页的URL , 并赋值 给 detail_urls , 结果是一个生成器, 我们调用 list 方法就可将其输出

部分结果、

爬取详情页

封面: 是一个 img 节点 其class 属性为 cover

名称: 是一个 h2 节点, 其内容为电影名称

类别: 是 span 节点, 其内容是电影类别。 span节点外侧是 button 节点,再外侧是class为 categories的div 节点

上映时间: 是span节点, 其内容包含上映时间, 外侧是 class 为 info 的div 节点。 另外提取结果中还多了 "上映" 二字, 我们可以用正则表达式把 日期提取出来

评分: 是一个p 节点, 其内容为 电影评分, p 节点的class属性为 score

剧情简介: 是一个 p 节点, 其内容便是剧情简介。 其外侧式class 为 drama 的div 节点

我们前面已经获取了详情页的 URL , 下面定义一个详情页的爬取方法,

def scrape_detail(url):
    return scrape_page(url)

这里定义了一个 scrape_detail 的方法, 接收一个 url 参数, 并通过 scape_page 方法获取详情页的HTML代码, 前面已经实现过了, 这里另外调用一次,一是这样会显得逻辑清晰,二是为了方便日后维护, 例如如果要输出错误日志, 增加预处理等, 都可以再这里实现

获取到详情页的代码后就是对详情页的解析了

def parse_detail(html):
    """
    parse detail page
    :param html: html of detail page
    :return: data
    """

# 这里是对每个内容的提取,做了正则化

    cover_pattern = re.compile(
        'class="item.*?<img.*?src="(.*?)".*?class="cover">', re.S)
    name_pattern = re.compile('<h2.*?>(.*?)</h2>')
    categories_pattern = re.compile(
        '<button.*?category.*?<span>(.*?)</span>.*?</button>', re.S)
    published_at_pattern = re.compile('(\d{4}-\d{2}-\d{2})\s?上映')
    drama_pattern = re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>', re.S)
    score_pattern = re.compile('<p.*?score.*?>(.*?)</p>', re.S)

# 这里将对应的正则带入页面代码, 提取出相应的内容

    cover = re.search(cover_pattern, html).group(
        1).strip() if re.search(cover_pattern, html) else None
    name = re.search(name_pattern, html).group(
        1).strip() if re.search(name_pattern, html) else None
    categories = re.findall(categories_pattern, html) if re.findall(
        categories_pattern, html) else []
    published_at = re.search(published_at_pattern, html).group(
        1) if re.search(published_at_pattern, html) else None
    drama = re.search(drama_pattern, html).group(
        1).strip() if re.search(drama_pattern, html) else None
    score = float(re.search(score_pattern, html).group(1).strip()
                  ) if re.search(score_pattern, html) else None

# 将提取出的内容以字典形式返回

    return {
        'cover': cover,
        'name': name,
        'categories': categories,
        'published_at': published_at,
        'drama': drama,
        'score': score
    }

这里定义了一个 parsse-detail 的方法, 接收一个参数 html 解析其中的内容,并以字典形式返回结果

保存数据

这里将提取到的数据以 JSON的格式保存到文本

import json
from os import makedirs
from os.path import exists

RESULTS_DIR = 'results'
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
def save_data(data):
    """
    save to json file
    :param data:
    :return:
    """
    name = data.get('name')
    data_path = f'{RESULTS_DIR}/{name}.json'
    json.dump(data, open(data_path, 'w', encoding='utf-8'),
              ensure_ascii=False, indent=2)

首先定义了保存数据的文件夹 RESULTS_DIR , 判断这个文件夹是否存在, 如果不存在则新建一个。

接着定义了保存数据的方法 save_data 其中首先是获取数据的name 字段,即电影的名称。将其作为JSON文件的名称, 然后构造JSON文件的路径 接着用JSON 的dump 方法将数据保存成文本格式,dump由两个参数,一个是 ensure_ascii 值为False, 可以保证中文字符在文件中能以正常的中文文本呈现, 而不是 unicode 字符, 另一个是 indent, 值为2, 设置了JSON数据的结果由两行缩进。

接下来改变一下 main 方法

def main():
    for page in range(1, TOTAL_PAGE + 1):
        index_html = scrape_index(page)
        detail_urls = parse_index(index_html)
    for detail_url in detail_urls:
        detail_html = scrape_detail(detail_url)
        data = parse_detail(detail_html)
        logging.info('get detail dat %s', data)
        logging.info('saving data to json file')
        save_data(data)
        logging.info('data saved successfully')
if __name__ == '__main__':
    main()

这里只是将前面输出的数据传入了保存数据的函数并没有多少改变

多进程加速

import multiprocessing
def main(page):
    """
    main process
    :return:
    """
    index_html = scrape_index(page)
    detail_urls = parse_index(index_html)
    for detail_url in detail_urls:
        detail_html = scrape_detail(detail_url)
        data = parse_detail(detail_html)
        logging.info('get detail data %s', data)
        logging.info('saving data to json file')
        save_data(data)
        logging.info('data saved successfully')

if __name__ == '__main__':
    pool = multiprocessing.Pool()
    pages = range(1, TOTAL_PAGE + 1)
    pool.map(main, pages)
    pool.close()

这里首先改变了一下 main 方法,在里面添加了一个page 参数, 用以表示页的页码。接着声明了一个进程池, 并声明pages 为所需要遍历的页码, 即 1-10 最后调用map 方法, 其第一个参数就是需要被调用的参数, 第二个参数就是 pages 即需要遍历的页码

这样就会一次遍历 pages 中的内容, 把1-10 这10个页码分别传递给main方法,并把每次的调用分别变成一个进程,加入进程池中,进程池根据当前的运行环境来决定运行多少个进程。 例如 8 核的, 那么进程池的大小就会默认为 8 这样就有 8 个进程并行运作

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1930544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32智能建筑能源管理系统教程

目录 引言环境准备智能建筑能源管理系统基础代码实现&#xff1a;实现智能建筑能源管理系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;能源管理与优化问题解决方案与优化收尾与总结 1. 引言 智能建筑能…

云计算数据中心(二)

目录 三、绿色节能技术&#xff08;一&#xff09;配电系统节能技术&#xff08;二&#xff09;空调系统节能技术&#xff08;三&#xff09;集装箱数据中心节能技术&#xff08;四&#xff09;数据中心节能策略和算法研究&#xff08;五&#xff09;新能源的应用&#xff08;六…

verilog刷题笔记

1、选择器实现方式 &#xff08;1&#xff09;case语句&#xff0c;注意default &#xff08;2&#xff09;if-else语言&#xff0c;注意else&#xff0c;有优先级 &#xff08;3&#xff09;三元运算符 &#xff1f; &#xff1a; 2、阻塞赋值/非阻塞赋值都是过程性赋值&a…

华为od机试真题 — 测试用例执行计划(Python)

题目描述 某个产品当前迭代周期内有N个特性&#xff08;F1, F2, ..., FN&#xff09;需要进行覆盖测试&#xff0c;每个特性都被评估了对应的优先级&#xff0c;特性使用其ID作为下标进行标识。 设计了M个测试用例&#xff08;T1, T2,...,TM&#xff09;&#xff0c;每个用例…

【JavaEE】HTTP协议(1)

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;上一篇文章&#xff1a;【JavaEE】网络原理—…

入坑树莓派(2)——树莓派4B与手机蓝牙通信

入坑树莓派(2)——树莓派4B与手机蓝牙通信 1、引言 在入坑树莓派(1)中已经搞掂了可视化问题。现在继续开展下一步,尝试与手机通信,一开始是想弄wifi连接的,但发现基于wifi的APP比较难弄,为了降低开发的难度,又因为树莓派板子自带蓝牙模块,所以直接选用蓝牙连接手机…

Qt窗口程序整理汇总

到今日为止&#xff0c;通过一个个案例的实验&#xff0c;逐步熟悉了 Qt6下 窗体界面开发的&#xff0c;将走过的路&#xff0c;再次汇总整理。 Qt Splash样式的登录窗https://blog.csdn.net/castlooo/article/details/140462768 Qt实现MDI应用程序https://blog.csdn.net/cast…

昇思25天学习打卡营第16天|Vision Transformer图像分类

本节使用Vision Transfomer完成图像分类 相关知识 Vision Transformer ViT是计算机视觉和自然语言处理两个领域的融合成果。它使用transformer架构来处理图像数据&#xff0c;这种架构原本是用于处理自然语言的。 ViT的主要思想是将图像分割成固定大小的块&#xff08;patch…

kafka开启kerberos和ACL

作者&#xff1a;恩慈 一、部署kafka-KB包 1&#xff0e;上传软件包 依次点击 部署中心----部署组件----上传软件包 选择需要升级的kafka版本并点击确定 2&#xff0e;部署kafka 依次点击部署中心----部署组件----物理/虚拟机部署----选择集群----下一步 选择手动部署-…

GB28181语音对讲实现

1.前提准备 1.1.首先将设备接入SIP网关服务 我这里使用的是开源的wvp-GB28181-pro项目&#xff0c;首先将设备接入到SIP网关服务。 配置信息如下&#xff1a; 1.2.修改设备配置 设备接入后&#xff0c;会自动注册到平台&#xff0c;可以在国标设备栏看到刚刚注册的设备信息…

K8S私有云裸金属服务器负载均衡器OpenELB——筑梦之路

OpenELB介绍 OpenELB 是一个专为裸机 Kubernetes 集群设计的开源负载均衡器实现。 在云服务环境中的 Kubernetes 集群里&#xff0c;通常可以用云服务提供商提供的负载均衡服务来暴露 Service&#xff0c;但是在本地没办法这样操作。而 OpenELB 可以让用户在裸金属服务器、边缘…

模块化和包管理工具

一&#xff0c;模块化 1.定义 将一个复杂的程序文件依据一定规则&#xff08;规范&#xff09;拆分成多个文件的过程称之为 模块化 其中拆分出的 每个文件就是一个模块 &#xff0c;模块的内部数据是私有的&#xff0c;不过模块可以暴露内部数据以便其他模块使用 2.模块化…

6.Dockerfile及Dockerfile常用指令

Dockerfile是构建docker镜像的脚本文件 Dockerfile有很多的指令构成&#xff0c;指令由上到下依次运行。 每一条指令就是一层镜像&#xff0c;层越多&#xff0c;体积就越大&#xff0c;启动速度也越慢 井号开头的行是注释行。指令写大写写小写都行&#xff0c;但一般都写为…

【Linux】多线程_9

文章目录 九、多线程10. 线程池 未完待续 九、多线程 10. 线程池 这里我没实现一些 懒汉单例模式 的线程池&#xff0c;并且包含 日志打印 的线程池&#xff1a; Makefile&#xff1a; threadpool:Main.ccg -o $ $^ -stdc11 -lpthread .PHONY:clean clean:rm -f threadpoolT…

前端开发常用命令行工具及操作命令(Node.js 和 npm、Yarn、vue、React、Git、Webpack)

在前端开发中&#xff0c;掌握常用的命令行工具和命令可以大大提高开发效率。接下来将介绍一些常用的前端命令行工具和命令&#xff0c;涵盖从项目初始化到构建和部署的各个环节。 1. Node.js 和 npm 安装 Node.js 和 npm 首先&#xff0c;需要安装 Node.js。安装 Node.js 时…

FreeRTOS的中断管理、临界资源保护、任务调度

什么是中断&#xff1f; 简介&#xff1a;让CPU打断正常运行的程序&#xff0c;转而去处理紧急的事件&#xff08;程序&#xff09;&#xff0c;就叫中断。 中断优先级分组设置 ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级&#xff0c;这个寄存器就是中断优先级…

硅纪元视角 | 微软开发全新AI模型,革新电子表格处理效率!

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…

在ArcGIS Pro中新建空图层的最快方法

01常规方法 一般情况下&#xff0c;如果我们想新建一个要素图层&#xff0c;常规方法是&#xff1a; 在目录框中&#xff0c;找一个gdb数据库&#xff0c;右键——新建——要素类&#xff1a; 设置好各种属性&#xff1a; 创建结果如下&#xff1a; 最后将要素类拖入地图中即…

openeuler 终端中文显示乱码、linux vim中文乱码

1、解决终端乱码 网上很多教程试了都不生效&#xff0c;以下方法有效&#xff1a; 确认终端支持中文显示&#xff1a; echo $LANG 输出应该包含 UTF-8&#xff0c;例如 en_US.UTF-8。如果不是&#xff0c;您可以通过以下命令设置为 UTF-8&#xff1a; export LANGzh_CN.UTF-8…

Rust RefCell<T> 和内部可变性模式

内部可变性&#xff08;Interior mutability&#xff09;是 Rust 中的一个设计模式&#xff0c;它允许你即使在有不可变引用时也可以改变数据&#xff0c;这通常是借用规则所不允许的。为了改变数据&#xff0c;该模式在数据结构中使用 unsafe 代码来模糊 Rust 通常的可变性和借…