【深入Scrapy实战】从登录到数据解析构建完整爬虫流程

文章目录

  • 1. 写在前面
  • 2. 抓包分析
  • 3. Scrapy提交登陆请求
  • 4. 列表与详情页面数据解析
  • 5. 中间件Middleware配置

【作者主页】:吴秋霖
【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作!
【作者推荐】:对JS逆向感兴趣的朋友可以关注《爬虫JS逆向实战》,对分布式爬虫平台感兴趣的朋友可以关注《分布式爬虫平台搭建与开发实战》
还有未来会持续更新的验证码突防、APP逆向、Python领域等一系列文章

1. 写在前面

  Scrapy是爬虫非常经典的一个框架,深受开发者喜爱!因其简洁高效的设计,被广泛选用于构建强大的爬虫工程。很多人会选择使用它来开发自己的爬虫工程。今天我将用一个论坛网站的示例来全面讲述Scrapy框架的使用

以前都是底层开始,现在不一样了,一上来都是框架。导致很多人是知其然,但不知其所以然。而忽略了底层原理的理解


目标网站(感兴趣的可以练练手)

aHR0cHM6Ly9mb3J1bS5heGlzaGlzdG9yeS5jb20v


这是一个国外的BBS论坛,随手挑的一个曾经写过的案例。前几年做舆情相关的项目,写的爬虫真的是很多,境内外社交媒体、论坛、新闻资讯

在这里插入图片描述

在这里插入图片描述

2. 抓包分析

  首先,我们打开这个网站,这个网站是要登陆的。我们先解决登陆这块,简单的构造一下登陆请求抓个包分析一下:

在这里插入图片描述

上图就是登陆请求提交的参数,接下来我们需要在Scrapy爬虫工程的Spider中构造并实现登陆功能

3. Scrapy提交登陆请求

  参数都都是明文的比较简单,唯一的一个sid也不是加密生成的,在HTML中就能够拿到

很多时候一些接口某些参数,你看起来是密文,但是并不一定就是加密算法生成的,很有可能在HTML或者其它接口响应中就能获取的到

sid获取如下:

在这里插入图片描述

现在我们开始编写Scrapy爬虫中登陆的这部分代码,实现代码如下所示:

def parse(self, response):text = response.headers['Set-Cookie']pa = re.compile("phpbb3_lzhqa_sid=(.*?);")sid = pa.findall(text)[0]response.meta['sid'] = sidlogin_url = 'https://forum.axishistory.com/ucp.php?mode=login'yield Request(login_url, meta=response.meta, callback=self.parse_login)def parse_login(self, response):sid=response.meta['sid']username ='用户名'password = '密码'formdata = {"username": username,"password": password,"sid": sid,"redirect": "index.php","login": "Login",}yield FormRequest.from_response(response, formid='login', formdata=formdata, callback=self.parse_after_login)

首先我们它通过parse函数从start_urls请求所响应的response中获取sid的值,然后继续交给parse_login的登陆函数实现模拟登陆

另外说一下formid这个参数,在HTML文档中,表单通常通过标签定义,并且可以包含id属性,这个id属性就是表单的ID,如下一个HTML的示例:

<form id="login" method="post" action="/login"><!-- 表单的其他字段 --><input type="text" name="username"><input type="password" name="password"><!-- 其他表单字段 --><input type="submit" value="Login">
</form>

在上面的这个例子中,标签有一个id属性,其值为“login”。所以,formid这个参数用于指定表单,去构造登陆提交请求

4. 列表与详情页面数据解析

  登陆处理完以后,我们就可以使用Scrapy爬虫继续对列表跟详情页构造请求并解析数据,这一部分的无非就是写XPATH规则了,基本对技术的要求并不高,如下使用XPATH测试工具编写列表页链接提取的规则:
在这里插入图片描述

Scrapy列表页代码实现如下:

def parse_page_list(self, response):pagination = response.meta.get("pagination", 1)details = response.xpath("//div[@class='inner']/ul/li")for detail in details:replies = detail.xpath("dl/dd[@class='posts']/text()").extract_first()views = detail.xpath("dl/dd[@class='views']/text()").extract_first()meta = response.metameta["replies"] = repliesmeta["views"] = viewsdetail_link = detail.xpath("dl//div[@class='list-inner']/a[@class='topictitle']/@href").extract_first()detail_title = detail.xpath("dl//div[@class='list-inner']/a[@class='topictitle']/text()").extract_first()meta["detail_title"] = detail_titleyield Request(response.urljoin(detail_link), callback=self.parse_detail, meta=response.meta)next_page = response.xpath("//div[@class='pagination']/ul/li/a[@rel='next']/@href").extract_first()if next_page and pagination < self.pagination_num:meta = response.metameta['pagination'] = pagination+1yield Request(response.urljoin(next_page), callback=self.parse_page_list, meta=meta)

self.pagination_num是一个翻页最大采集数的配置,这个自行设定即可

通过列表页我们拿到了所有贴文的链接,我们并在代码的最后使用了yield对列表页发起了请求,<font 并通过color=#ff0033 size=3>callback=self.parse_detail交给解析函数去提取数据

首先我们定义在项目的items.py文件中定义Item数据结构,主要帖子跟评论的,如下所示:

class AccountItem(Item):account_url = Field()                # 账号urlaccount_id = Field()                 # 账号idaccount_name = Field()               # 账号名称nick_name = Field()                  # 昵称website_name = Field()               # 论坛名account_type = Field()               # 账号类型,固定forumlevel = Field()                      # 账号等级account_description = Field()        # 账号描述信息account_followed_num = Field()       # 账号关注数account_followed_list = Field()      # 账号关注id列表account_focus_num = Field()          # 账号粉丝数account_focus_list = Field()         # 账号粉丝id列表regist_time = Field()                # 账号注册时间forum_credits = Field()              # 论坛积分/经验值location = Field()                   # 地区post_num = Field()                   # 发帖数reply_num = Field()                  # 跟帖数msg_type = Field()area = Field()class PostItem(Item):type = Field()                 # "post"post_id = Field()              # 帖子idtitle = Field()                # 帖子标题content = Field()              # 帖子内容website_name = Field()         # 论坛名category = Field()             # 帖子所属版块url = Field()                  # 帖子urllanguage = Field()             # 语种, zh_cn|en|esrelease_time = Field()         # 发布时间account_id = Field()            # 发帖人idaccount_name = Field()          # 发帖人账号名page_view_num = Field()        # 帖子浏览数comment_num = Field()          # 帖子回复数like_num = Field()             # 帖子点赞数quote_from =Field()            # 被转载的帖子idlocation_info = Field()        # 发帖地理位置信息images_url = Field()           # 帖子图片链接image_file = Field()           # 帖子图片存储路径msg_type = Field()area = Field()class CommentItem(Item):type = Field()                 # "comment"website_name = Field()         # 论坛名post_id = Field()comment_id = Field()content = Field()              # 回帖内容release_time = Field()         # 回帖时间account_id = Field()           # 帖子回复人idaccount_name = Field()         # 回帖人名称comment_level = Field()        # 回帖层级parent_id = Field()            # 回复的帖子或评论idlike_num = Field()             # 回帖点赞数comment_floor = Field()        # 回帖楼层images_url = Field()           # 评论图片链接image_file = Field()           # 评论图片存储路径msg_type = Field()area = Field()

接下来我们需要编写贴文内容的数据解析代码,解析函数代码实现如下所示:

def parse_detail(self, response):dont_parse_post = response.meta.get("dont_parse_post")category = " < ".join(response.xpath("//ul[@id='nav-breadcrumbs']/li//span[@itemprop='title']/text()").extract()[1:])if dont_parse_post is None:msg_ele = response.xpath("//div[@id='page-body']//div[@class='inner']")[0]post_id = msg_ele.xpath("div//h3/a/@href").extract_first(default='').strip().replace("#p", "")post_item = PostItem()post_item["url"] = response.urlpost_item['area'] = self.namepost_item['msg_type'] = u"贴文"post_item['type'] = u"post"post_item["post_id"] = post_idpost_item["language"] = 'en'post_item["website_name"] = self.allowed_domains[0]post_item["category"] = categorypost_item["title"] = response.meta.get("detail_title")post_item["account_name"] = msg_ele.xpath("div//strong/a[@class='username']/text()").extract_first(default='').strip()post_item["content"] = "".join(msg_ele.xpath("div//div[@class='content']/text()").extract()).strip()post_time = "".join(msg_ele.xpath("div//p[@class='author']/text()").extract()).strip()post_item["release_time"] = dateparser.parse(post_time).strftime('%Y-%m-%d %H:%M:%S')post_item["collect_time"] = dateparser.parse(str(time.time())).strftime('%Y-%m-%d %H:%M:%S')user_link =msg_ele.xpath("div//strong/a[@class='username']/@href").extract_first(default='').strip()account_id = "".join(re.compile("&u=(\d+)").findall(user_link))post_item["account_id"] = account_idpost_item["comment_num"] = response.meta.get("replies")post_item["page_view_num"] = response.meta.get("views")images_urls = msg_ele.xpath("div//div[@class='content']//img/@src").extract() or ""post_item["images_url"] = [response.urljoin(url) for url in images_urls]post_item["image_file"] = self.image_path(post_item["images_url"])post_item["language"] = 'en'post_item["website_name"] = self.nameresponse.meta["post_id"] = post_idresponse.meta['account_id'] = post_item["account_id"]response.meta["account_name"] = post_item["account_name"]full_user_link = response.urljoin(user_link)yield Request(full_user_link, meta=response.meta, callback=self.parse_account_info)for comment_item in self.parse_comments(response):yield comment_itemcomment_next_page = response.xpath(u"//div[@class='pagination']/ul/li/a[@rel='next']/@href").extract_first()if comment_next_page:response.meta["dont_parse_post"] = 1next_page_link = response.urljoin(comment_next_page)yield Request(next_page_link, callback=self.parse_detail, meta=response.meta)

贴文内容的下方就是评论信息,上面代码中我们拿到评论的链接comment_next_page,直接继续发送请求解析评论内容:

在这里插入图片描述

def parse_comments(self, response):comments = response.xpath("//div[@id='page-body']//div[@class='inner']")if response.meta.get("dont_parse_post") is None:comments = comments[1:]for comment in comments:comment_item = CommentItem()comment_item['type'] = "comment"comment_item['area'] = self.namecomment_item['msg_type'] = u"评论"comment_item['post_id'] = response.meta.get("post_id")comment_item["parent_id"] = response.meta.get("post_id")comment_item["website_name"] = self.allowed_domains[0]user_link =comment.xpath("div//strong/a[@class='username']/@href").extract_first(default='').strip()account_id = "".join(re.compile("&u=(\d+)").findall(user_link))comment_item['comment_id'] = comment.xpath("div//h3/a/@href").extract_first(default='').strip().replace("#p","")comment_item['account_id'] = account_idcomment_item['account_name'] = comment.xpath("div//strong/a[@class='username']/text()").extract_first(default='').strip()comment_time = "".join(comment.xpath("div//p[@class='author']/text()").extract()).strip()if not comment_time:continuecomment_level_text = comment.xpath("div//div[@id='post_content%s']//a[contains(@href,'./viewtopic.php?p')]/text()" % comment_item['comment_id']).extract_first(default='')comment_item['comment_level'] = "".join(re.compile("\d+").findall(comment_level_text))comment_item['release_time'] = dateparser.parse(comment_time).strftime('%Y-%m-%d %H:%M:%S')comment_content_list = "".join(comment.xpath("div//div[@class='content']/text()").extract()).strip()comment_item['content'] = "".join(comment_content_list)response.meta['account_id'] = comment_item["account_id"]response.meta["account_name"] = comment_item["account_name"]full_user_link = response.urljoin(user_link)yield Request(full_user_link, meta=response.meta, callback=self.parse_account_info)

评论信息采集中还有一个针对评论用户信息采集的功能,通过调用parse_account_info函数进行采集,实现代码如下所示:

def parse_account_info(self, response):about_item = AccountItem()about_item["account_id"] = response.meta["account_id"]about_item["account_url"] = response.urlabout_item["account_name"] = response.meta["account_name"]about_item["nick_name"] = ""about_item["website_name"] = self.allowed_domains[0]about_item["account_type"] = "forum"about_item["level"] = ""account_description = "".join(response.xpath("//div[@class='inner']/div[@class='postbody']//text()").extract())about_item["account_description"] = account_descriptionabout_item["account_followed_num"] = ""about_item["account_followed_list"] = ""about_item["account_focus_num"] = ""about_item["account_focus_list"] = ""regist_time = "".join(response.xpath("//dl/dt[text()='Joined:']/following-sibling::dd[1]/text()").extract())about_item["regist_time"] = dateparser.parse(regist_time).strftime('%Y-%m-%d %H:%M:%S')about_item["forum_credits"] = ""location = "".join(response.xpath("//dl/dt[text()='Location:']/following-sibling::dd[1]/text()").extract())about_item["location"] = locationpost_num_text = response.xpath("//dl/dt[text()='Total posts:']/following-sibling::dd[1]/text()[1]").extract_first(default='')post_num = post_num_text.replace(",",'').strip("|").strip()about_item["post_num"] = post_numabout_item["reply_num"] = ""about_item["msg_type"] = 'account'about_item["area"] = self.nameyield about_item

最后从帖子到评论再到账号信息,层层采集与调用拿到完整的一个JSON结构化数据,进行yield到数据库

5. 中间件Middleware配置

  因为是国外的论坛网站案例,所以这里我们需要使用我们的Middleware来解决这个问题:

class ProxiesMiddleware():logfile = logging.getLogger(__name__)def process_request(self, request, spider):self.logfile.debug("entry ProxyMiddleware")try:# 依靠meta中的标记,来决定是否需要使用proxyproxy_addr = spider.proxyif proxy_addr:if request.url.startswith("http://"):request.meta['proxy'] = "http://" + proxy_addr  # http代理elif request.url.startswith("https://"):request.meta['proxy'] = "https://" + proxy_addr  # https代理except Exception as e:exc_type, exc_obj, exc_tb = sys.exc_info()fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]self.logfile.warning(u"Proxies error: %s, %s, %s, %s" %(exc_type, e, fname, exc_tb.tb_lineno))

settings文件中配置开启Middleware:

DOWNLOADER_MIDDLEWARES = {'forum.middlewares.ProxiesMiddleware': 100,
}

  好了,到这里又到了跟大家说再见的时候了。创作不易,帮忙点个赞再走吧。你的支持是我创作的动力,希望能带给大家更多优质的文章

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

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

相关文章

python django 小程序图书借阅源码

开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…

使用ssh在本地环境(Windows)连接虚拟机以及其中的docker容器

配置虚拟机防火墙 防火墙的一系列操作需要root权限&#xff0c;默认是没有root密码的&#xff0c;所以首先需要设置root密码&#xff1a; sudo passwd root按提示完成root密码设置 切换到root账户 su root启用22端口并重启防火墙 firewall-cmd --permanent --add-port22/tc…

【NI-RIO入门】CompactRIO介绍及环境安装

CompactRIO是什么&#xff1f; CompactRIO系统提供了高处理性能、传感器专用I/O和紧密集成的软件工具&#xff0c;使其成为工业物联网、监测和控制应用的理想之选。实时处理器提供可靠&#xff0c;可预测的行为&#xff0c;而FPGA在需要高速逻辑和精确定时的较小任务上表现出色…

Microsoft Visual Studio 2019下载及安装流程记录

第一周任务&#xff1a; 1.笔记本上安装vc2019的环境 2.再把OpenCV安装上 3.根据网上的教程&#xff0c;试着写几个opencv的程序 一、安装Visual Studio 2019社区版 首先先完成安装vc2019的环境&#xff0c; 因为&#xff1a; Microsoft Visual C是用于C编程的工具集合&am…

机器学习笔记 - Ocr识别中的CTC算法原理概述

一、文字识别 在文本检测步骤中,分割出了文本区域。现在需要识别这些片段中存在哪些文本。 机器学习笔记 - Ocr识别中的文本检测EAST网络概述-CSDN博客文章浏览阅读300次。在 EAST 网络的这个分支中,它合并了 VGG16 网络不同层的特征输出。现在,该层之后的特征大小将等于 p…

matlab 坡度滤波算法地面分割

目录 一、算法原理1、实现流程2、参考文献二、代码实现三、结果展示四、测试数据一、算法原理 1、实现流程 1、格网示意图 2、计算格网行列数 公式中的特殊符号为向上取整,

el-tree结合el-switch实现状态切换

<template><div><el-col :span"24"><el-card class"tree-card"><div class"sketch_content selectFile"><span class"span_title">组织列表 </span><div style"display: flex; jus…

《崩坏:星穹铁道》1.5仙舟罗浮-绥园全宝箱攻略

大家好&#xff0c;我是闲游盒小盒子&#xff0c;本篇来说下崩铁1.5版本仙舟罗浮-绥园的全宝箱攻略&#xff0c;共有19个宝箱加1个扑满&#xff1b;做完间章可获取前14个普通宝箱加2个精英怪宝箱&#xff0c;以及1个扑满&#xff1b;完成《狐斋志异》全任务可获得另外3个宝箱。…

壹基金宣传进瑞金河背街社区 安全家园项目防灾减灾深入人心

11月16日下午&#xff0c;瑞金赋能公益、蓝天救援队等联合象湖镇河背街社区开展家庭安全计划社区活动包挑战赛活动暨壹基金安全家园项目防灾减灾宣传社区行活动。活动得到了救助儿童会北京代表处、壹基金、艾特公益、益心益意公益的指导&#xff0c;得到了阿里巴巴公益平台广大…

读像火箭科学家一样思考笔记04_第一性原理(下)

1. 来自无形规则的阻力 1.1. 无形规则 1.1.1. 僵化成规则的不必要习惯和行为 1.1.2. 不像有形的书面规则 1.1.2.1. 书面规则出现在标准操作流程中&#xff0c;可以修改或删除 1.1.3. 成文的规则可能会抗拒变革&#xff0c;但无形规则却更加顽固 1.1.4. 我们为强加在自己身…

【Q2—30min】

1.socket服务端创建过程 socket是应用层与TCP/IP协议族通信的中间软件抽象层&#xff0c;它是一组接口。在设计模式中&#xff0c;Socket其实就是一个门面模式&#xff0c;它把复杂的TCP/IP协议族隐藏在Socket接口后面&#xff0c;对用户来说&#xff0c;一组简单的接口就是全部…

从0开始学习JavaScript--JavaScript使用Promise

JavaScript中的异步编程一直是开发中的重要话题。传统的回调函数带来了回调地狱和代码可读性的问题。为了解决这些问题&#xff0c;ES6引入了Promise&#xff0c;一种更现代、更灵活的异步编程解决方案。本文将深入探讨JavaScript中如何使用Promise&#xff0c;通过丰富的示例代…

spider 网页爬虫中的 AWS 实例数据获取问题及解决方案

前言 AAWS实例数据对于自动化任务、监控、日志记录和资源管理非常重要。开发人员和运维人员可以通过AWS提供的API和控制台访问和管理这些数据&#xff0c;以便更好地管理和维护他们在AWS云上运行的实例。然而&#xff0c;在使用 spider 框架进行网页爬取时&#xff0c;我们常常…

.Net6 部署到IIS示例

基于FastEndpoints.Net6 框架部署到IIS 环境下载与安装IIS启用与配置访问网站 环境下载与安装 首先下载环境安装程序&#xff0c;如下图所示,根据系统位数选择x86或者x64进行下载安装,网址&#xff1a;Download .NET 6.0。 IIS启用与配置 启用IIS服务 打开控制面板&#xff…

【Linux】【开发】使用sed命令遇到的乱码问题

&#x1f41a;作者简介&#xff1a;花神庙码农&#xff08;专注于Linux、WLAN、TCP/IP、Python等技术方向&#xff09;&#x1f433;博客主页&#xff1a;花神庙码农 &#xff0c;地址&#xff1a;https://blog.csdn.net/qxhgd&#x1f310;系列专栏&#xff1a;Linux技术&…

【论文阅读笔记】Supervised Contrastive Learning

【论文阅读笔记】Supervised Contrastive Learning 摘要 自监督批次对比方法扩展到完全监督的环境中&#xff0c;以有效利用标签信息提出两种监督对比损失的可能版本 介绍 交叉熵损失函数的不足之处&#xff0c;对噪声标签的不鲁棒性和可能导致交叉的边际&#xff0c;降低了…

键盘快捷键工具Keyboard Maestro mac中文版介绍

Keyboard Maestro mac是一款键盘快捷键工具&#xff0c;它可以帮助用户通过自定义快捷键来快速完成各种操作&#xff0c;提高工作效率。Keyboard Maestro支持多种快捷键组合&#xff0c;包括单键、双键、三键、四键组合等&#xff0c;用户可以根据自己的习惯进行设置。此外&…

各类语言真实性能比较列表

这篇文章是我所做或将要做的所有真实世界性能比较的索引。如果你对想要看到的其他真实世界案例有建议&#xff0c;请在评论中添加。 用例 1 — JWT 验证 & MySQL 查询 该用例包括&#xff1a; 从授权头部获取 JWT验证 JWT 并从声明中获取电子邮件使用电子邮件执行 MySQL…

【计算机网络笔记】路由算法之链路状态路由算法

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

C++二分算法:找到最接近目标值的函数值

本文涉及的基础知识点 二分查找算法合集 题目 Winston 构造了一个如上所示的函数 func 。他有一个整数数组 arr 和一个整数 target &#xff0c;他想找到让 |func(arr, l, r) - target| 最小的 l 和 r 。 请你返回 |func(arr, l, r) - target| 的最小值。 请注意&#xff0c…