
文章采集调用
【爆款文章】集成服务与RPA的强强联袂,速来围观!
采集交流 • 优采云 发表了文章 • 0 个评论 • 75 次浏览 • 2022-08-17 14:29
金蝶云·苍穹V5.0新特性系列文章持续更新
每工作日早上07:45准时推送
为你带来最新产品资讯
老师,业务上有个需求是把某异构系统的采购订单集成到星瀚系统中,可对方系统不提供接口,没办法获取数据啊。
我的想法是用RPA机器人自动登录该异构系统获取采购订单信息,保存到Excel文件里面,然后再到集成服务云使用离线导入功能手工上传文件,设置好字段映射关系之后加载到星瀚当中。
但手工设置费时费力,有没有办法让这个流程自动化完成而不需要人工参与呢?
开发小白
集成专家
很简单,服务流程最新发布特性支持配置RPA节点。只需稍微改造RPA机器人,让他多做一点事情(稍后揭晓),再把RPA组装到服务流程中即可实现RPA集成。
通过该方案,只需执行一个服务流程就能完成数据的获取、转换和加载步骤了~
听起来很棒,迫不及待想请老师指教了呢。
开发小白
集成专家
那就让我们赶紧开始吧!
适用版本
该功能适用版本为金蝶云·苍穹V4.0.016及以上。
特性展示
RPA是通过模拟用户界面操作的方式来完成自动化的一项技术。通过配置集成服务云服务流程的RPA节点,可以在集成服务中嵌入RPA流程,将集成云从各个系统获取到的数据信息作用于更广泛的业务场景。
接下来,小编将以某异构系统的采购订单集成到星瀚采购订单为例,为大家介绍服务流程如何实现RPA集成。
01 整体思路
通过RPA模拟前台操作,登录异构系统,打开采购订单,获取采购订单数据并上传Excel文件到FTP服务器中。
然后,集成服务云通过服务编排组装该RPA节点后,获取相应数据,其他节点进行数据的转换、映射和加载,完成后将执行结果通过通知节点短信通知到用户。
服务流程集成RPA案例示例
02实现步骤
步骤一:RPA控制台参数配置
使用该功能的前提是【系统服务云】→【配置工具】→【系统参数】中配置了RPA控制台的参数信息。如下图所示:
注:没有此参数请联系RPA部门提供。
RPA控制台系统集成参数
步骤二:用户授权与查看
配置服务流程的用户需要授权RPA通用角色。如下图所示:
RPA通用角色
完成角色授权后,登录RPA控制台即可查看当前用户可使用的RPA流程。如下图所示:
查看RPA流程
步骤三:设计RPA流程
RPA流程中,通过登录异构系统、打开采购订单、获取采购订单数据和保存数据并上传Excel文件四个子流程完成异构系统的采购订单信息的数据获取。
(注:此案例根据需求场景改造RPA流程,增加上传数据文件到FTP服务器子流程,非必选改造。)
RPA主流程 - 登录/打开采购订单/获取数据/上传
各子流程配置详情如下图所示:
左右滑动查看更多>>(从左至右依次为登录异构系统、打开采购订单、获取采购订单数据、保存并上传Excel文件)
步骤四:配置服务流程
创建服务流程后,在流程图中配置RPA节点,选择RPA流程和执行流程的RPA机器人。如下图所示:
服务流程中嵌入RPA节点
RPA节点详情
步骤五:运行监控
双击流程实例中的RPA节点,可查看RPA任务执行详情,如下图所示:
服务流程实例与RPA节点
RPA节点执行详情
亮点价值
亮点一:集成服务流程和RPA强强联合
打通集成服务云与RPA控制台,轻松实现异构系统的集成与标准化流程的执行、多渠道采集、加工与处理数据,提高工作效率,助力企业业务集成自动化。
亮点二:流程配置简单,执行过程可追溯
服务流程中可以直接配置一个或多个RPA节点,通过用户获取RPA流程与RPA机器人信息,在服务流程执行时自动触发RPA机器人的执行,并且支持在流程实例中查看RPA任务详情、RPA录屏和日志信息等。
亮点三:RPA流程的业务角色更直观
服务流程即业务集成流程,RPA节点作为其中一部分,可以直观地看到RPA如何在整条业务链中发挥作用。
相关链接
关于RPA集成的详细内容可参考下方链接:
RPA集成指南V1.0:
RPA节点录入币别到异构系统示例:
服务流程调用RPA视频:
划重点
1) 集成服务云提供了RPA集成功能。通过该功能,集成服务流程中可组装RPA机器人,在集成业务流程中实现标准化流程的自动执行。
2) 集成服务云的RPA集成具备以下特点: 查看全部
【爆款文章】集成服务与RPA的强强联袂,速来围观!
金蝶云·苍穹V5.0新特性系列文章持续更新
每工作日早上07:45准时推送
为你带来最新产品资讯
老师,业务上有个需求是把某异构系统的采购订单集成到星瀚系统中,可对方系统不提供接口,没办法获取数据啊。
我的想法是用RPA机器人自动登录该异构系统获取采购订单信息,保存到Excel文件里面,然后再到集成服务云使用离线导入功能手工上传文件,设置好字段映射关系之后加载到星瀚当中。
但手工设置费时费力,有没有办法让这个流程自动化完成而不需要人工参与呢?
开发小白
集成专家
很简单,服务流程最新发布特性支持配置RPA节点。只需稍微改造RPA机器人,让他多做一点事情(稍后揭晓),再把RPA组装到服务流程中即可实现RPA集成。
通过该方案,只需执行一个服务流程就能完成数据的获取、转换和加载步骤了~
听起来很棒,迫不及待想请老师指教了呢。
开发小白
集成专家
那就让我们赶紧开始吧!
适用版本
该功能适用版本为金蝶云·苍穹V4.0.016及以上。
特性展示
RPA是通过模拟用户界面操作的方式来完成自动化的一项技术。通过配置集成服务云服务流程的RPA节点,可以在集成服务中嵌入RPA流程,将集成云从各个系统获取到的数据信息作用于更广泛的业务场景。
接下来,小编将以某异构系统的采购订单集成到星瀚采购订单为例,为大家介绍服务流程如何实现RPA集成。

01 整体思路
通过RPA模拟前台操作,登录异构系统,打开采购订单,获取采购订单数据并上传Excel文件到FTP服务器中。
然后,集成服务云通过服务编排组装该RPA节点后,获取相应数据,其他节点进行数据的转换、映射和加载,完成后将执行结果通过通知节点短信通知到用户。
服务流程集成RPA案例示例
02实现步骤
步骤一:RPA控制台参数配置
使用该功能的前提是【系统服务云】→【配置工具】→【系统参数】中配置了RPA控制台的参数信息。如下图所示:
注:没有此参数请联系RPA部门提供。
RPA控制台系统集成参数
步骤二:用户授权与查看
配置服务流程的用户需要授权RPA通用角色。如下图所示:
RPA通用角色
完成角色授权后,登录RPA控制台即可查看当前用户可使用的RPA流程。如下图所示:
查看RPA流程
步骤三:设计RPA流程
RPA流程中,通过登录异构系统、打开采购订单、获取采购订单数据和保存数据并上传Excel文件四个子流程完成异构系统的采购订单信息的数据获取。
(注:此案例根据需求场景改造RPA流程,增加上传数据文件到FTP服务器子流程,非必选改造。)
RPA主流程 - 登录/打开采购订单/获取数据/上传
各子流程配置详情如下图所示:

左右滑动查看更多>>(从左至右依次为登录异构系统、打开采购订单、获取采购订单数据、保存并上传Excel文件)
步骤四:配置服务流程
创建服务流程后,在流程图中配置RPA节点,选择RPA流程和执行流程的RPA机器人。如下图所示:
服务流程中嵌入RPA节点
RPA节点详情
步骤五:运行监控
双击流程实例中的RPA节点,可查看RPA任务执行详情,如下图所示:
服务流程实例与RPA节点
RPA节点执行详情
亮点价值
亮点一:集成服务流程和RPA强强联合
打通集成服务云与RPA控制台,轻松实现异构系统的集成与标准化流程的执行、多渠道采集、加工与处理数据,提高工作效率,助力企业业务集成自动化。
亮点二:流程配置简单,执行过程可追溯
服务流程中可以直接配置一个或多个RPA节点,通过用户获取RPA流程与RPA机器人信息,在服务流程执行时自动触发RPA机器人的执行,并且支持在流程实例中查看RPA任务详情、RPA录屏和日志信息等。
亮点三:RPA流程的业务角色更直观
服务流程即业务集成流程,RPA节点作为其中一部分,可以直观地看到RPA如何在整条业务链中发挥作用。
相关链接
关于RPA集成的详细内容可参考下方链接:
RPA集成指南V1.0:
RPA节点录入币别到异构系统示例:
服务流程调用RPA视频:
划重点
1) 集成服务云提供了RPA集成功能。通过该功能,集成服务流程中可组装RPA机器人,在集成业务流程中实现标准化流程的自动执行。
2) 集成服务云的RPA集成具备以下特点:
红队从资产收集到打点
采集交流 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-08-12 04:28
最近想总结一下,在红队渗透拿到一个目标名或者刷src时候,怎么快速信息收集和批量检测来打到一个点,往往在实际项目中就是拼手速。
信息收集到打点大致我就分为
企业信息结构收集敏感信息收集域名主动被动收集整理域名ip资产扫描检测打点
其中每一步需要收集好几个方面的信息,手动很累也很慢
1.企业信息结构收集
企业信息结构收集包括对查询目标企业的公司信息,涉及到哪些主站域名,有哪些控股很多的子公司,这些子公司涉及到哪些域名,然后再进行备案反查,你又会得到一些新的公司,同理也能再次得到一些新的主站域名,将这些进行整理---->得到一批待爆破的域名。
还有的就是除了这些查到的主站域名,往往企业会有app、公众号、小程序这些资产,也要对这些资产进行收集,然后你又拿到了一批域名。
手动查询的话从以下查询
天眼查 查企业/子公司/域名/公众号 https://www.tianyancha.com/爱企查 https://aiqicha.baidu.com/企查查询 https://www.qcc.com/启信宝 https://www.qixin.com/
工具:
推荐cSubsidiary利用天眼查查询企业子公司https://github.com/canc3s/cSubsidiary<br />还有pigat:https://github.com/teamssix/pigat<br />公众号和app的收集:https://github.com/wgpsec/ENSc ... an_GO go版本
2.敏感信息收集
利用搜索引擎、github等托管平台配合一些dorks就可以搜到很多信息。
熟知的googlehack,gitdork,网盘泄露等等。
敏感信息一共要搜集这个几个方面:
googlehack语法github泄露目标人员姓名/手机/邮箱
1.googlehack
但比如googlehack,你需要搜的好几条语法加上域名
比如:
site:*.domain.cominurl:domain.comintitle:keywordkeyword filetyle:doc|pdf
一个域名可以配合多个语法搜,那么多域名手动输入搜很慢,推荐工具:
https://github.com/r00tSe7en/GoogleHackingTool 在线Google Hacking 小工具https://www.exploit-db.com/google-hacking-database 语法,自己可以脚本里批量搜
2.github泄露敏感信息:
一些常用github dorks,直接搜对应目标信息:
xxxxx.com "Authorization" #"Authorization: Bearer"xxxxx.com "filename:vim_settings.xml"xxxxx.com "language:PHP"
也可以在github对各种信息搜索,比如文件类型
filename:manifest.xmlfilename:travis.ymlfilename:vim_settings.xmlfilename:databasefilename:prod.exs NOT prod.secret.exsfilename:prod.secret.exsfilename:.npmrc _authfilename:.dockercfg authfilename:WebServers.xmlfilename:.bash_history filename:sftp-config.jsonfilename:sftp.json path:.vscodefilename:secrets.yml passwordfilename:.esmtprc passwordfilename:passwd path:etcfilename:dbeaver-data-sources.xmlpath:sites databases passwordfilename:config.php dbpasswdfilename:prod.secret.exsfilename:configuration.php JConfig passwordfilename:.sh_history
包含关键字的指定语言:
language:python usernamelanguage:php usernamelanguage:sql usernamelanguage:html passwordlanguage:perl passwordlanguage:shell usernamelanguage:java apiHOMEBREW_GITHUB_API_TOKEN language:shell
搜API/KEYS/TOEKNS关键字:
api_key“api keys”authorization_bearer:oauthauthauthenticationclient_secretapi_token:“api token”client_idpassworduser_passworduser_passpasscodeclient_secretsecretpassword hashOTPuser auth
很多关键字可以搜,还是批量搜高效,工具:
https://github.com/obheda12/Gi ... dorks
这类工具需要设置git令牌,附上gitrob过程,踩坑:不要下relase ,自己编译最好:
git clone https://github.com/michenriksen/gitrob.gitgo mod init #to use go mod 如果报错 运行go mod init github.com/michenriksen/gitrobrm Gopkg* #remove the old stuffgo build #to build it<br /><br />./build.sh
设置git令牌
set GITROB_ACCESS_TOKEN=xxxxx
使用后可以查看图形界面的结果:
3. 目标人员姓名/手机/邮箱
通过开源信息收集目标人员姓名/手机/邮箱,为后面打点做字典做准备。
https://github.com/laramies/theHarvester
通过搜索引擎、PGP服务器以及SHODAN数据库收集用户的email,子域名,主机,雇员名,开放端口和banner信息。
使用:
-d 开关用于定义域名,-l 用于限制结果数量
theHarvester -d kali.org -l 200 -banubis,baidu,pentesttools,projectdiscovery,qwant,rapiddns,rocketreach,securityTrails,spyse,sublist3r,threatcrowd,threatminer,trello,twitter,urlscan,virustotal,yahoo,zoomeye,bing,binaryedge,bingapi,bufferoverun,censys,certspotter,crtsh,dnsdumpster,duckduckgo,fullhunt,github-code,google,hackertarget,hunter,intelx,linkedin,linkedin_links,n45ht,omnisint,otx
按github跑就是了,但是有点坑点:
配置api-keys在/etc/theHarvester 目录下api-keys.yaml填入对应的api key即可
有个坑点是key:后要加个空格在放key字符串,不然跑不起来
人员邮箱字典的构造:
https://github.com/pry0cc/Goog ... ed.rb
还可以使用一些社工信息来做字典,这样的工具很多了,用一个就够了没必要用全部:Cupp/Cewl
https://github.com/r3nt0n/bopscrkpython3 bopscrk.py -i
3. 域名主动被动收集
域名主动信息收集内容就有点杂了。
通过1、2点我们拿到了一批等待爆破的域名和人员的信息,以及泄露的一些敏感信息(运气好的话用泄露的信息已经打到点了。)
现在需要对域名进行whois信息查询、dns域名正反查询、子域名探测爆破三个方面收集。
1.whois信息查询
whois需要查询域名的whois,然后根据whois信息来查询历史和反查,这样你就得到了一些邮箱和可疑域名。
查域名信息没什么说的,主要看网址注册人、到期记录、创建域的时间、名称服务器和联系信息等,查最新的一般都是托管的信息,而查看历史信息就有可能查到真实联系人邮箱电话等:
一些常见whois查询,手动的时候可以查询:
https://domaineye.com/reverse- ... whois
除了正向查询whois,还要查询whois历史信息:
以下几个网站允许用户访问连接的 WHOIS 数据库以进行调查。这些记录是十多年来对有关域注册的有用数据进行网络爬取的结果:
https://whois.domaintools.com/ ... .com/
whois历史信息查询不能错过,明显可以在whois历史信息中看真实邮箱并反查而不是目前托管的邮箱,以及非托管的dns服务器:
whois 信息反查
通过历史whois信息找到真实邮箱or组织名,再反查域名,又可以得到一批资产:
other:
https://www.reversewhois.io/
整理一下whois分了三步,先whois查询一个域名,然后对查询的信息进行历史whois查询和反查,最后得到一批邮箱和域名。手动知道过程就行,实际做项目用工具批量查了整理:
https://github.com/xugj-gits/domain-tool 批量whois查询https://github.com/melbadry9/WhoEnum
2.dns域名正向反向查询
dns域名查询分两个部分,历史记录和ip反查:
DNS历史记录(doamin2ips)
Dnsdumpster 是一个在线实用程序,我们使用它来查找子域、目标的 DNS 记录。
VT也是可以看dns数据信息的:
ip反查(ip2domains)
同ip查询多个解析到这个ip的域名,寻找更多web资产
https://viewdns.info/reverseip/
https://dnslytics.com/
ip反查也可以使用dig、nslookup、host命令完成:
工具推荐:
https://www.infobyip.com/ipbulklookup.php 批量ip反查https://github.com/Sma11New/ip2domain 国内域名推荐ip2domain,会查询权重、ICP备案等
通过dns查询,我们拿到了一些域名和可疑ip段
3.子域名探测爆破
没啥好说的,主要是收集的渠道全、过滤泛解析。
常见手法爆破子域名、证书透明度、搜索引擎、信息泄露、ASN号等等,很多工具已经做了这些工作
https://github.com/shmilylty/O ... redns
4. 整理域名ip资产
到这里大致的收集就结束了,就是要对收集结果进行整理,通过上面收集能拿到:
一批待探测存活的域名一批待确定的ip段一些邮箱,姓名,手机号一些敏感文件、信息、通用密码(敏感信息收集阶段看脸)
整理后大致如上,有一步需要做的就是把收集的这些域名,转成ip段,但是是需要判断这个ip属不属于cdn,属不属于泛解析的ip,然后转成ip后要判断ip段的权重,哪些段才可能是目标主要的C段。
https://github.com/EdgeSecurityTeam/Eeyes 对subdomain数据处理、获取其中真实IP并整理成c段https://github.com/canc3s/cIPR 整理后查看权重
5. 扫描检测打点
这步就开始快速打点了。
上面整理后的资产,需要我们探测的是一批域名和一批C段
域名需要做的事:
探测存活title、banner提取、指纹识别爬虫、目录轻量扫描、轻量漏扫
C段需要做的事:
扫描端口,探测存活将扫的web和非web进行分类,把扫到的web资产加入到域名需要做的事,和对待域名没区别将扫到的非web(数据库/远程登录协议)进行爆破,比如mysql爆破,rdp爆破
一批域名和一批C段就这样做不同的事,来先探测是否有脆弱的点,最后才是回归常规web,一个站一个站的去撕
一些工具:
https://github.com/broken5/WebAliveScan web存活判断https://github.com/fadinglr/EHole 红队重点攻击系统指纹探测工具https://github.com/k8gege/K8CScan 漏洞扫描、密码爆破https://github.com/b1gcat/DarkEye 主机发现+爆破https://github.com/Adminisme/ServerScan 高并发网络扫描、服务探测工具https://github.com/dean2021/titlesearch 批量抓取域名title工具https://github.com/pmiaowu/PmWebDirScan 批量扫目录备份
还有的就是一些大家都熟知的xray,vulmap之类的漏洞,批量轻量去扫描一下即可。
把上面的几个步骤,工具串起来,行成快速信息收集,快速探测打点,最好写个贯穿流程的工具调用的脚本,自己写过效果不错但代码不好就不拿出来丢人了,基本这样过一遍就容易打到一些比较脆弱的点。 查看全部
红队从资产收集到打点
最近想总结一下,在红队渗透拿到一个目标名或者刷src时候,怎么快速信息收集和批量检测来打到一个点,往往在实际项目中就是拼手速。
信息收集到打点大致我就分为
企业信息结构收集敏感信息收集域名主动被动收集整理域名ip资产扫描检测打点
其中每一步需要收集好几个方面的信息,手动很累也很慢
1.企业信息结构收集
企业信息结构收集包括对查询目标企业的公司信息,涉及到哪些主站域名,有哪些控股很多的子公司,这些子公司涉及到哪些域名,然后再进行备案反查,你又会得到一些新的公司,同理也能再次得到一些新的主站域名,将这些进行整理---->得到一批待爆破的域名。
还有的就是除了这些查到的主站域名,往往企业会有app、公众号、小程序这些资产,也要对这些资产进行收集,然后你又拿到了一批域名。
手动查询的话从以下查询
天眼查 查企业/子公司/域名/公众号 https://www.tianyancha.com/爱企查 https://aiqicha.baidu.com/企查查询 https://www.qcc.com/启信宝 https://www.qixin.com/
工具:
推荐cSubsidiary利用天眼查查询企业子公司https://github.com/canc3s/cSubsidiary<br />还有pigat:https://github.com/teamssix/pigat<br />公众号和app的收集:https://github.com/wgpsec/ENSc ... an_GO go版本
2.敏感信息收集
利用搜索引擎、github等托管平台配合一些dorks就可以搜到很多信息。
熟知的googlehack,gitdork,网盘泄露等等。
敏感信息一共要搜集这个几个方面:
googlehack语法github泄露目标人员姓名/手机/邮箱
1.googlehack
但比如googlehack,你需要搜的好几条语法加上域名
比如:
site:*.domain.cominurl:domain.comintitle:keywordkeyword filetyle:doc|pdf
一个域名可以配合多个语法搜,那么多域名手动输入搜很慢,推荐工具:
https://github.com/r00tSe7en/GoogleHackingTool 在线Google Hacking 小工具https://www.exploit-db.com/google-hacking-database 语法,自己可以脚本里批量搜
2.github泄露敏感信息:
一些常用github dorks,直接搜对应目标信息:
xxxxx.com "Authorization" #"Authorization: Bearer"xxxxx.com "filename:vim_settings.xml"xxxxx.com "language:PHP"
也可以在github对各种信息搜索,比如文件类型
filename:manifest.xmlfilename:travis.ymlfilename:vim_settings.xmlfilename:databasefilename:prod.exs NOT prod.secret.exsfilename:prod.secret.exsfilename:.npmrc _authfilename:.dockercfg authfilename:WebServers.xmlfilename:.bash_history filename:sftp-config.jsonfilename:sftp.json path:.vscodefilename:secrets.yml passwordfilename:.esmtprc passwordfilename:passwd path:etcfilename:dbeaver-data-sources.xmlpath:sites databases passwordfilename:config.php dbpasswdfilename:prod.secret.exsfilename:configuration.php JConfig passwordfilename:.sh_history
包含关键字的指定语言:
language:python usernamelanguage:php usernamelanguage:sql usernamelanguage:html passwordlanguage:perl passwordlanguage:shell usernamelanguage:java apiHOMEBREW_GITHUB_API_TOKEN language:shell
搜API/KEYS/TOEKNS关键字:
api_key“api keys”authorization_bearer:oauthauthauthenticationclient_secretapi_token:“api token”client_idpassworduser_passworduser_passpasscodeclient_secretsecretpassword hashOTPuser auth
很多关键字可以搜,还是批量搜高效,工具:
https://github.com/obheda12/Gi ... dorks
这类工具需要设置git令牌,附上gitrob过程,踩坑:不要下relase ,自己编译最好:
git clone https://github.com/michenriksen/gitrob.gitgo mod init #to use go mod 如果报错 运行go mod init github.com/michenriksen/gitrobrm Gopkg* #remove the old stuffgo build #to build it<br /><br />./build.sh
设置git令牌
set GITROB_ACCESS_TOKEN=xxxxx
使用后可以查看图形界面的结果:

3. 目标人员姓名/手机/邮箱
通过开源信息收集目标人员姓名/手机/邮箱,为后面打点做字典做准备。
https://github.com/laramies/theHarvester
通过搜索引擎、PGP服务器以及SHODAN数据库收集用户的email,子域名,主机,雇员名,开放端口和banner信息。
使用:
-d 开关用于定义域名,-l 用于限制结果数量
theHarvester -d kali.org -l 200 -banubis,baidu,pentesttools,projectdiscovery,qwant,rapiddns,rocketreach,securityTrails,spyse,sublist3r,threatcrowd,threatminer,trello,twitter,urlscan,virustotal,yahoo,zoomeye,bing,binaryedge,bingapi,bufferoverun,censys,certspotter,crtsh,dnsdumpster,duckduckgo,fullhunt,github-code,google,hackertarget,hunter,intelx,linkedin,linkedin_links,n45ht,omnisint,otx
按github跑就是了,但是有点坑点:
配置api-keys在/etc/theHarvester 目录下api-keys.yaml填入对应的api key即可
有个坑点是key:后要加个空格在放key字符串,不然跑不起来
人员邮箱字典的构造:
https://github.com/pry0cc/Goog ... ed.rb
还可以使用一些社工信息来做字典,这样的工具很多了,用一个就够了没必要用全部:Cupp/Cewl
https://github.com/r3nt0n/bopscrkpython3 bopscrk.py -i
3. 域名主动被动收集
域名主动信息收集内容就有点杂了。
通过1、2点我们拿到了一批等待爆破的域名和人员的信息,以及泄露的一些敏感信息(运气好的话用泄露的信息已经打到点了。)
现在需要对域名进行whois信息查询、dns域名正反查询、子域名探测爆破三个方面收集。
1.whois信息查询
whois需要查询域名的whois,然后根据whois信息来查询历史和反查,这样你就得到了一些邮箱和可疑域名。
查域名信息没什么说的,主要看网址注册人、到期记录、创建域的时间、名称服务器和联系信息等,查最新的一般都是托管的信息,而查看历史信息就有可能查到真实联系人邮箱电话等:
一些常见whois查询,手动的时候可以查询:
https://domaineye.com/reverse- ... whois
除了正向查询whois,还要查询whois历史信息:
以下几个网站允许用户访问连接的 WHOIS 数据库以进行调查。这些记录是十多年来对有关域注册的有用数据进行网络爬取的结果:
https://whois.domaintools.com/ ... .com/
whois历史信息查询不能错过,明显可以在whois历史信息中看真实邮箱并反查而不是目前托管的邮箱,以及非托管的dns服务器:
whois 信息反查
通过历史whois信息找到真实邮箱or组织名,再反查域名,又可以得到一批资产:
other:
https://www.reversewhois.io/
整理一下whois分了三步,先whois查询一个域名,然后对查询的信息进行历史whois查询和反查,最后得到一批邮箱和域名。手动知道过程就行,实际做项目用工具批量查了整理:
https://github.com/xugj-gits/domain-tool 批量whois查询https://github.com/melbadry9/WhoEnum

2.dns域名正向反向查询
dns域名查询分两个部分,历史记录和ip反查:
DNS历史记录(doamin2ips)
Dnsdumpster 是一个在线实用程序,我们使用它来查找子域、目标的 DNS 记录。
VT也是可以看dns数据信息的:
ip反查(ip2domains)
同ip查询多个解析到这个ip的域名,寻找更多web资产
https://viewdns.info/reverseip/
https://dnslytics.com/
ip反查也可以使用dig、nslookup、host命令完成:
工具推荐:
https://www.infobyip.com/ipbulklookup.php 批量ip反查https://github.com/Sma11New/ip2domain 国内域名推荐ip2domain,会查询权重、ICP备案等
通过dns查询,我们拿到了一些域名和可疑ip段
3.子域名探测爆破
没啥好说的,主要是收集的渠道全、过滤泛解析。
常见手法爆破子域名、证书透明度、搜索引擎、信息泄露、ASN号等等,很多工具已经做了这些工作
https://github.com/shmilylty/O ... redns
4. 整理域名ip资产
到这里大致的收集就结束了,就是要对收集结果进行整理,通过上面收集能拿到:
一批待探测存活的域名一批待确定的ip段一些邮箱,姓名,手机号一些敏感文件、信息、通用密码(敏感信息收集阶段看脸)
整理后大致如上,有一步需要做的就是把收集的这些域名,转成ip段,但是是需要判断这个ip属不属于cdn,属不属于泛解析的ip,然后转成ip后要判断ip段的权重,哪些段才可能是目标主要的C段。
https://github.com/EdgeSecurityTeam/Eeyes 对subdomain数据处理、获取其中真实IP并整理成c段https://github.com/canc3s/cIPR 整理后查看权重
5. 扫描检测打点
这步就开始快速打点了。
上面整理后的资产,需要我们探测的是一批域名和一批C段
域名需要做的事:
探测存活title、banner提取、指纹识别爬虫、目录轻量扫描、轻量漏扫
C段需要做的事:
扫描端口,探测存活将扫的web和非web进行分类,把扫到的web资产加入到域名需要做的事,和对待域名没区别将扫到的非web(数据库/远程登录协议)进行爆破,比如mysql爆破,rdp爆破
一批域名和一批C段就这样做不同的事,来先探测是否有脆弱的点,最后才是回归常规web,一个站一个站的去撕
一些工具:
https://github.com/broken5/WebAliveScan web存活判断https://github.com/fadinglr/EHole 红队重点攻击系统指纹探测工具https://github.com/k8gege/K8CScan 漏洞扫描、密码爆破https://github.com/b1gcat/DarkEye 主机发现+爆破https://github.com/Adminisme/ServerScan 高并发网络扫描、服务探测工具https://github.com/dean2021/titlesearch 批量抓取域名title工具https://github.com/pmiaowu/PmWebDirScan 批量扫目录备份
还有的就是一些大家都熟知的xray,vulmap之类的漏洞,批量轻量去扫描一下即可。
把上面的几个步骤,工具串起来,行成快速信息收集,快速探测打点,最好写个贯穿流程的工具调用的脚本,自己写过效果不错但代码不好就不拿出来丢人了,基本这样过一遍就容易打到一些比较脆弱的点。
文章采集调用 【】lotlib
采集交流 • 优采云 发表了文章 • 0 个评论 • 123 次浏览 • 2022-08-04 09:09
文章采集调用本地浏览器特定链接,ip就用转发地址好了。其他调用平台,一般也是接口上获取。各个视频站点可能使用smi等格式数据来互相传播,arctime支持。互联网时代,随着网速增快,付费会员日益增多,付费视频,付费文件所占比例在不断提高。
我不知道怎么用,但是我知道现在视频调试都是一个程序来监控的,
<p>importarctimeimportjsonimportmatplotlib。pyplotaspltimportnumpyasnp#获取官方文件大小,并转换成acrobat格式defget_ac_transactions(data_content):url=''returnr' 查看全部
文章采集调用 【】lotlib
文章采集调用本地浏览器特定链接,ip就用转发地址好了。其他调用平台,一般也是接口上获取。各个视频站点可能使用smi等格式数据来互相传播,arctime支持。互联网时代,随着网速增快,付费会员日益增多,付费视频,付费文件所占比例在不断提高。
我不知道怎么用,但是我知道现在视频调试都是一个程序来监控的,
<p>importarctimeimportjsonimportmatplotlib。pyplotaspltimportnumpyasnp#获取官方文件大小,并转换成acrobat格式defget_ac_transactions(data_content):url=''returnr'
天** | 使用selenium做数据采集
采集交流 • 优采云 发表了文章 • 0 个评论 • 133 次浏览 • 2022-07-30 11:09
哈工程管工在读博士,擅长数据采集&挖掘。
文末有代码可供下载
马云在接受CNBC(美国消费者新闻与商业频道)采访时提出:“整个世界将变成数据,我认为这还是只是数据时代的开始。新浪潮即将来临,很多就业机会将被夺走。有些人会赶上潮流,变得更加富有和成功。但是对于那些落后的人,未来将是痛苦的。”就小编看来,这种说法在人文社科研究当中也同样适用。在当前数以万计甚至数以十万计研究样本“遍地走”的时代,若我们还拘泥于传统的“小样本”研究(比如样本量为100多的调查问卷数据等),不仅难以跟随时代的脚步,还会逐渐丧失学术竞争力、从而被时代淘汰。那么,究竟该如何获取属于自己的大样本数据呢?今天小编就带大家用selenium库来爬取国内某知名第三方企业信息平台(天**)的企业工商信息。
一、自动打开网站页面
首先,数据爬取的第一步是利用selenium库启动浏览器,打开我们的目标网站。部分代码
# @Author : Jacob-ZHANG<br />import requests,base64<br />from PIL import Image<br />import csv,re<br />from selenium import webdriver<br />import time,random<br /><br />#1启动浏览器。<br />#win<br />browser=webdriver.Chrome(executable_path='driver/chromedriver.exe')<br />#mac<br />#browser=webdriver.Chrome(executable_path='driver/chromedriver')<br /><br />#2加入这个脚本可以避免被识别<br />browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br />"source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """ })<br /><br />#3延迟10s启动<br />browser.implicitly_wait(10)<br /><br />#4利用谷歌浏览器打开目标网页<br />browser.get('https://pro.xxxxxx.com/searchx')<br /><br />#5将窗口最大化<br />browser.maximize_window()<br /><br />#6给网页一些时间加载<br />time.sleep(random.randint(1, 2))<br />...<br />...<br />...<br />
非常简单,如下所示。
需要说明的是:
二、模拟登陆
天**反扒的第一关便是需要登录才能够查看具体的页面信息。相比于利用复杂JS逆向技术完成登陆而言,利用selenium库模拟人的操作、从而实现网站自动化登陆的做法则显得更为简单易行。从下图来看,我们需要利用selenium库来完成“点击密码登录(切换到密码登录页面,也即下图所示页面)-向账号对话框内输入账号-向密码对话框内输入密码-向验证码对话框内输入验证码-点击登录”等一系列操作后,才能登录到网站的信息页面,获取自己要想的数据。
对于网站的登录我们提供了以下两种方法:一种是自动化登录;另一种则是手动登录。
2.1 自动化登录
首先,我们先来看看较为复杂的自动化登录。要想实现网页的自动化登录,其关键在于利用 「外部力量」 来识别验证码并完成导入。具体而言,我们首先需要定位验证码在网页当中的元素位置,其次利用截图软件根据验证码元素位置来截取验证码图片,再次利用外部库对验证码图片进行识别,最后将识别出的验证码录入对话框。自动化登录的具体过程可以分为 get_code_image函数 和 parse_code函数 两个步骤进行,具体代码如下所示。其中,验证码的解析小编是调用了百度AI的开源库进行的。另外,需要注意的是,利用selenium库打开的登录页面一开始是不显示验证码的,必须向账号框和密码框输入内容以后,它才会显示验证码。因此,对于验证码的识别和录入,我们将它放在了所有操作中的最后部分。
parse_code函数
def parse_code():<br /> #用百度API解析图片<br /> request_url = "https://aip.baidubce.com/rest/ ... %3Bbr /> f = open('temp/验证码.png', 'rb')<br /> img = base64.b64encode(f.read())<br /> params = {"image": img}<br /> access_token = '24.a7fbbfb9dcab2e1054cc827f09d09234.2592000.1625930266.282335-19004069'<br /><br /> request_url = request_url + "?access_token=" + access_token<br /> headers = {'content-type': 'application/x-www-form-urlencoded'}<br /> response = requests.post(request_url, data=params, headers=headers)<br /> <br /> #得到解析结果<br /> dictionary=response.json()<br /> <br /> #得到验证码<br /> yanzhengma=dictionary['words_result'][0]['words']<br /> <br /> #录入验证码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/input').send_keys(yanzhengma)<br /> <br /> # 点击登录按钮<br /> time.sleep(random.randint(1, 2))<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[8]').click()<br /><br />
parse_code函数
def get_code_image():<br /> # 1启动浏览器。<br /> browser = webdriver.Chrome()<br /> # 2加入这个脚本可以避免被识别<br /> browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br /> "source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """})<br /> # 3延迟10s启动<br /> browser.implicitly_wait(10)<br /> <br /> # 4利用谷歌浏览器打开目标网页<br /> browser.get('https://pro.xxxxxx.com/searchx')<br /> <br /> # 5将窗口最大化<br /> browser.maximize_window()<br /> <br /> # 6给网页一些时间加载<br /> time.sleep(random.randint(1, 2))<br /><br /> #将页面从快捷登录切换到密码登录<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/div[1]/div[2]').click()<br /> time.sleep(0.5)<br /> <br /> #输入账号<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[2]/input').send_keys(<br /> '******')<br /> time.sleep(random.randint(1, 2))<br /> <br /> # 输入密码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[4]/input').send_keys('*******')<br /> time.sleep(random.randint(1, 2))<br /> browser.save_screenshot('temp/屏幕.png')#截图整个页面】<br /> <br /> #定位验证码x,y坐标<br /> left_angle=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img').location<br /> image=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img')<br /> <br /> #获取验证码的长和宽<br /> size=image.size<br /> <br /> #设定我们需要截取的位置<br /> rangle = (int(left_angle['x']), int(left_angle['y'] ), int(left_angle['x'] + size['width'] + 230),<br /><br /> int(left_angle['y'] + size['height'] + 300))<br /> <br /> #打开截图<br /> open_image=Image.open('temp/屏幕.png')<br /> <br /> #从图片中截取我们需要的的区域<br /> jietu=open_image.crop(rangle)<br /> jietu.save('temp/验证码.png')<br />
接下来,我们再来看看如何实现手动登录。相比于自动化登录,手动登录的操作更为简单。具体地,我们只要在完成自动打开网站页面的代码后加入input()函数,然后自己手动向网站的对话框内输入账号、密码、验证码并点击登录,就可以进入到网站的信息页面。
三、获取自己想要的数据
完成登陆之后,就会跳转到如下页面。然后,大家就可以根据自己的目标继续撰写属于自己的“个性化代码”了。下面,小编以获取31个省市的特定类型的企业数据为例,给大家分享一下自己获取数据的过程。
其实,代码撰写的逻辑很简单。首先要做的就是先选中我们要爬取的目标城市。下来就是根据自己的需求来定制个性化的筛选标准。以小编自己的需求为例,先通过点击高级模式,向企业名称对话框里输入关键词,比如医院(当然,大家也可以通过限定行业来挑选目标);然后,去掉机构类型中已勾选的企业,选择事业单位;接下来,勾选全部企业类型;最后点击查看结果。
至此就完成了筛选,得到了满足我们要求的所有企业(见下图1)。接下来,我们要做的就是遍历每一页里的每一家企业,然后获取企业页面信息(见下图2)中自己想要的数据了。
由于后续代码较长,就不在这里一一列举了。有需要的小伙伴可以在后台留言,然后向xx索取。
说明
对于初学者而言,直接上手可能比较难,建议先收藏本文,待熟练掌握爬虫可以实验本文的代码。
如果想复现本文代码,需熟悉
本文教程&代码免费分享,但作者时间和精力宝贵,可能无法做到一一指导,尽请包涵。
代码链接: 提取码: ob2j
精选文章
<p style="margin: 0px;padding: 0px;clear: both;min-height: 1em;outline: 0px;max-width: 100%;color: rgb(63, 63, 63);font-size: 15px;letter-spacing: 0px;white-space: normal;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;line-height: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;">长期招募小伙伴
从符号到嵌入:计算社会科学的两种文本表示<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
推荐 | 社科(经管)文本分析快速指南<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
使用cntext训练Glove词嵌入模型
cntext库 | 关于DUTIR被污染解决办法<br style="margin: 0px;padding: 0px;" />
EmoBank | 中文维度情感词典<br />
sklearnex库 | 两行代码百倍加速你的机器学习代码<br style="margin: 0px;padding: 0px;" />
认知的测量 | 向量距离vs语义投影
Wordify | 发现和区分消费者词汇的工具
karateclub库 | 计算社交网络中节点的向量
视频专栏课 | Python网络爬虫与文本分析
扩增内置pkl | 欢迎各位向cntext库分享情感词典<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
文本分析 | 中国企业高管团队创新注意力(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
LIWC vs Python | 文本分析之词典统计法略讲(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
PNAS | 文本网络分析&文化桥梁Python代码实现<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
Wordify | 发现和区分消费者词汇的工具
BERTopic库 | 使用预训练模型做话题建模
tomotopy | 速度最快的LDA主题模型
文本分析方法在《管理世界》(2021.5)中的应用
Wow~70G上市公司定期报告数据集
doccano|为机器学习建模做数据标注
使用WeasyPrint自动生成pdf报告文件
100min视频 | Python文本分析与会计
在jupyter内运行R代码</p> 查看全部
天** | 使用selenium做数据采集
哈工程管工在读博士,擅长数据采集&挖掘。
文末有代码可供下载
马云在接受CNBC(美国消费者新闻与商业频道)采访时提出:“整个世界将变成数据,我认为这还是只是数据时代的开始。新浪潮即将来临,很多就业机会将被夺走。有些人会赶上潮流,变得更加富有和成功。但是对于那些落后的人,未来将是痛苦的。”就小编看来,这种说法在人文社科研究当中也同样适用。在当前数以万计甚至数以十万计研究样本“遍地走”的时代,若我们还拘泥于传统的“小样本”研究(比如样本量为100多的调查问卷数据等),不仅难以跟随时代的脚步,还会逐渐丧失学术竞争力、从而被时代淘汰。那么,究竟该如何获取属于自己的大样本数据呢?今天小编就带大家用selenium库来爬取国内某知名第三方企业信息平台(天**)的企业工商信息。
一、自动打开网站页面
首先,数据爬取的第一步是利用selenium库启动浏览器,打开我们的目标网站。部分代码
# @Author : Jacob-ZHANG<br />import requests,base64<br />from PIL import Image<br />import csv,re<br />from selenium import webdriver<br />import time,random<br /><br />#1启动浏览器。<br />#win<br />browser=webdriver.Chrome(executable_path='driver/chromedriver.exe')<br />#mac<br />#browser=webdriver.Chrome(executable_path='driver/chromedriver')<br /><br />#2加入这个脚本可以避免被识别<br />browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br />"source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """ })<br /><br />#3延迟10s启动<br />browser.implicitly_wait(10)<br /><br />#4利用谷歌浏览器打开目标网页<br />browser.get('https://pro.xxxxxx.com/searchx')<br /><br />#5将窗口最大化<br />browser.maximize_window()<br /><br />#6给网页一些时间加载<br />time.sleep(random.randint(1, 2))<br />...<br />...<br />...<br />
非常简单,如下所示。
需要说明的是:
二、模拟登陆
天**反扒的第一关便是需要登录才能够查看具体的页面信息。相比于利用复杂JS逆向技术完成登陆而言,利用selenium库模拟人的操作、从而实现网站自动化登陆的做法则显得更为简单易行。从下图来看,我们需要利用selenium库来完成“点击密码登录(切换到密码登录页面,也即下图所示页面)-向账号对话框内输入账号-向密码对话框内输入密码-向验证码对话框内输入验证码-点击登录”等一系列操作后,才能登录到网站的信息页面,获取自己要想的数据。

对于网站的登录我们提供了以下两种方法:一种是自动化登录;另一种则是手动登录。
2.1 自动化登录
首先,我们先来看看较为复杂的自动化登录。要想实现网页的自动化登录,其关键在于利用 「外部力量」 来识别验证码并完成导入。具体而言,我们首先需要定位验证码在网页当中的元素位置,其次利用截图软件根据验证码元素位置来截取验证码图片,再次利用外部库对验证码图片进行识别,最后将识别出的验证码录入对话框。自动化登录的具体过程可以分为 get_code_image函数 和 parse_code函数 两个步骤进行,具体代码如下所示。其中,验证码的解析小编是调用了百度AI的开源库进行的。另外,需要注意的是,利用selenium库打开的登录页面一开始是不显示验证码的,必须向账号框和密码框输入内容以后,它才会显示验证码。因此,对于验证码的识别和录入,我们将它放在了所有操作中的最后部分。
parse_code函数
def parse_code():<br /> #用百度API解析图片<br /> request_url = "https://aip.baidubce.com/rest/ ... %3Bbr /> f = open('temp/验证码.png', 'rb')<br /> img = base64.b64encode(f.read())<br /> params = {"image": img}<br /> access_token = '24.a7fbbfb9dcab2e1054cc827f09d09234.2592000.1625930266.282335-19004069'<br /><br /> request_url = request_url + "?access_token=" + access_token<br /> headers = {'content-type': 'application/x-www-form-urlencoded'}<br /> response = requests.post(request_url, data=params, headers=headers)<br /> <br /> #得到解析结果<br /> dictionary=response.json()<br /> <br /> #得到验证码<br /> yanzhengma=dictionary['words_result'][0]['words']<br /> <br /> #录入验证码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/input').send_keys(yanzhengma)<br /> <br /> # 点击登录按钮<br /> time.sleep(random.randint(1, 2))<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[8]').click()<br /><br />
parse_code函数
def get_code_image():<br /> # 1启动浏览器。<br /> browser = webdriver.Chrome()<br /> # 2加入这个脚本可以避免被识别<br /> browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br /> "source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """})<br /> # 3延迟10s启动<br /> browser.implicitly_wait(10)<br /> <br /> # 4利用谷歌浏览器打开目标网页<br /> browser.get('https://pro.xxxxxx.com/searchx')<br /> <br /> # 5将窗口最大化<br /> browser.maximize_window()<br /> <br /> # 6给网页一些时间加载<br /> time.sleep(random.randint(1, 2))<br /><br /> #将页面从快捷登录切换到密码登录<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/div[1]/div[2]').click()<br /> time.sleep(0.5)<br /> <br /> #输入账号<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[2]/input').send_keys(<br /> '******')<br /> time.sleep(random.randint(1, 2))<br /> <br /> # 输入密码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[4]/input').send_keys('*******')<br /> time.sleep(random.randint(1, 2))<br /> browser.save_screenshot('temp/屏幕.png')#截图整个页面】<br /> <br /> #定位验证码x,y坐标<br /> left_angle=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img').location<br /> image=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img')<br /> <br /> #获取验证码的长和宽<br /> size=image.size<br /> <br /> #设定我们需要截取的位置<br /> rangle = (int(left_angle['x']), int(left_angle['y'] ), int(left_angle['x'] + size['width'] + 230),<br /><br /> int(left_angle['y'] + size['height'] + 300))<br /> <br /> #打开截图<br /> open_image=Image.open('temp/屏幕.png')<br /> <br /> #从图片中截取我们需要的的区域<br /> jietu=open_image.crop(rangle)<br /> jietu.save('temp/验证码.png')<br />
接下来,我们再来看看如何实现手动登录。相比于自动化登录,手动登录的操作更为简单。具体地,我们只要在完成自动打开网站页面的代码后加入input()函数,然后自己手动向网站的对话框内输入账号、密码、验证码并点击登录,就可以进入到网站的信息页面。
三、获取自己想要的数据
完成登陆之后,就会跳转到如下页面。然后,大家就可以根据自己的目标继续撰写属于自己的“个性化代码”了。下面,小编以获取31个省市的特定类型的企业数据为例,给大家分享一下自己获取数据的过程。

其实,代码撰写的逻辑很简单。首先要做的就是先选中我们要爬取的目标城市。下来就是根据自己的需求来定制个性化的筛选标准。以小编自己的需求为例,先通过点击高级模式,向企业名称对话框里输入关键词,比如医院(当然,大家也可以通过限定行业来挑选目标);然后,去掉机构类型中已勾选的企业,选择事业单位;接下来,勾选全部企业类型;最后点击查看结果。
至此就完成了筛选,得到了满足我们要求的所有企业(见下图1)。接下来,我们要做的就是遍历每一页里的每一家企业,然后获取企业页面信息(见下图2)中自己想要的数据了。
由于后续代码较长,就不在这里一一列举了。有需要的小伙伴可以在后台留言,然后向xx索取。
说明
对于初学者而言,直接上手可能比较难,建议先收藏本文,待熟练掌握爬虫可以实验本文的代码。
如果想复现本文代码,需熟悉
本文教程&代码免费分享,但作者时间和精力宝贵,可能无法做到一一指导,尽请包涵。
代码链接: 提取码: ob2j
精选文章
<p style="margin: 0px;padding: 0px;clear: both;min-height: 1em;outline: 0px;max-width: 100%;color: rgb(63, 63, 63);font-size: 15px;letter-spacing: 0px;white-space: normal;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;line-height: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;">长期招募小伙伴
从符号到嵌入:计算社会科学的两种文本表示<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
推荐 | 社科(经管)文本分析快速指南<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
使用cntext训练Glove词嵌入模型
cntext库 | 关于DUTIR被污染解决办法<br style="margin: 0px;padding: 0px;" />
EmoBank | 中文维度情感词典<br />
sklearnex库 | 两行代码百倍加速你的机器学习代码<br style="margin: 0px;padding: 0px;" />
认知的测量 | 向量距离vs语义投影
Wordify | 发现和区分消费者词汇的工具
karateclub库 | 计算社交网络中节点的向量
视频专栏课 | Python网络爬虫与文本分析
扩增内置pkl | 欢迎各位向cntext库分享情感词典<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
文本分析 | 中国企业高管团队创新注意力(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
LIWC vs Python | 文本分析之词典统计法略讲(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
PNAS | 文本网络分析&文化桥梁Python代码实现<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
Wordify | 发现和区分消费者词汇的工具
BERTopic库 | 使用预训练模型做话题建模
tomotopy | 速度最快的LDA主题模型
文本分析方法在《管理世界》(2021.5)中的应用
Wow~70G上市公司定期报告数据集
doccano|为机器学习建模做数据标注
使用WeasyPrint自动生成pdf报告文件
100min视频 | Python文本分析与会计
在jupyter内运行R代码</p>
文章采集调用 《抽奖》某次红蓝对抗之Solr-RCE实战绕过
采集交流 • 优采云 发表了文章 • 0 个评论 • 113 次浏览 • 2022-07-28 03:27
文章首发于:先知社区
前言
在某次红蓝对抗过程中。
要结束的时候突然发现了扫描器爆出了
Solr-RCE-CVE-2019-0192漏洞。
但是进行测试过程中,发现存在各种各样的问题
绕过1
进行测试,发现目标只可以执行单命令,返回字段比较少的命令。
输入:whoami、ipconfig
执行dir,无法执行。
不出网
想着直接执行ps直接上线就好了,各种尝试之后,后知后觉发现对方不出网
写websgell
发现目标不出网的时候,只有写webshell这一条路子可以走了。
但是目标只能执行个别命令还无法解决。
dir无法执行。陷入了沉思,加入单双引号也不行。
根据以前的内网经验是不是系统无法默认调用到dir.exe。
那么 cmd /c dir是不是可以。
惊奇的发现,可以完美的执行命令。
通过dir,找到目录。进行写马尝试。
但是发现目标路由规则写死了,无法直接访问到.jsp的文件。
刚开始以为是根目录的问题。
发现不止根目录,常用的css/js和img下面的也不行。
后续在webshell中看到,翻文件看到了有类似路由机制的验证
言归正传
在执行rce的时候,找到了solr的目录。发现这里的.jsp是没有这个验证的。
利用命令执行进行找到该位置,进行写文件。
问题来了。echo 写入一直无法写入。。
问题解决
把这里的特殊字符进行特殊字符编码,即可成功写入。
又遇到一个问题。jsp的马子都有%号,这里不论怎么做 %就是写不进去。
差点放弃。找不到不带%的马子。
柳暗花明
但是想到了上午利用过的Certutil可以进行编码解码
这样就没有特殊字符了。
完全没问题。
刚开始一点一点追加,发现下面的会写进去两行
看了一下就最后有一个+号,没有特殊字符。
全部直接写进去。
然后decode进行解码,完美。
访问但是是500.不过确很开心,因为确实写上来。
接下来解决为啥500就可以了。
type 123.jsp
查看一下。
发现最后decode的时候,少了一个>
本地测试,是没有这个问题的。可能是目标一次性字符长度的问题。
这里很简单了。
追加一下就可以了。
连接成功
验证
文末福利:
《安卓Frida逆向与协议分析》
本书翔实地介绍流行的Frida工具在安卓逆向工程中的应用,内容包括:如何安装和使用Frida、基本环境的搭建、Frida-tools、Frida脚本、Frida API、批量自动化Trace和分析、RPC远程方法调用、在无须逆向算法具体实现的情况下对Frida工具的调用,并提供了大量App逆向与协议分析案例,书中还介绍了更加稳定的Xposed框架的使用方法,以及从安卓源码开始定制属于自己的抓包沙箱,打造无法被绕过的抓包环境等内容。
本书案例丰富,注重实操,适合安卓应用安全工程师、安卓逆向分析工程师、爬虫工程师以及大数据采集和分析工程师使用。
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。
福利时间:7月25日-7月31日
2. 活动规则:
① 扫描下方二维码参与抽奖或公众号后台回复安卓逆向即可参与;
② 必要条件:转发本文到朋友圈,抽奖前不可删除;
③ 开奖结束后,请中奖小伙伴及时将中奖信息和朋友圈转发记录发送到公众号后台联系,超过24小时未领取的视为自动放弃哈!!!
未满足②条件但被抽中,则获奖资格会被取消哦
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。 查看全部
文章采集调用 《抽奖》某次红蓝对抗之Solr-RCE实战绕过
文章首发于:先知社区
前言
在某次红蓝对抗过程中。
要结束的时候突然发现了扫描器爆出了
Solr-RCE-CVE-2019-0192漏洞。
但是进行测试过程中,发现存在各种各样的问题
绕过1
进行测试,发现目标只可以执行单命令,返回字段比较少的命令。
输入:whoami、ipconfig
执行dir,无法执行。
不出网
想着直接执行ps直接上线就好了,各种尝试之后,后知后觉发现对方不出网
写websgell
发现目标不出网的时候,只有写webshell这一条路子可以走了。
但是目标只能执行个别命令还无法解决。
dir无法执行。陷入了沉思,加入单双引号也不行。
根据以前的内网经验是不是系统无法默认调用到dir.exe。
那么 cmd /c dir是不是可以。
惊奇的发现,可以完美的执行命令。
通过dir,找到目录。进行写马尝试。
但是发现目标路由规则写死了,无法直接访问到.jsp的文件。

刚开始以为是根目录的问题。
发现不止根目录,常用的css/js和img下面的也不行。
后续在webshell中看到,翻文件看到了有类似路由机制的验证
言归正传
在执行rce的时候,找到了solr的目录。发现这里的.jsp是没有这个验证的。
利用命令执行进行找到该位置,进行写文件。
问题来了。echo 写入一直无法写入。。
问题解决
把这里的特殊字符进行特殊字符编码,即可成功写入。
又遇到一个问题。jsp的马子都有%号,这里不论怎么做 %就是写不进去。
差点放弃。找不到不带%的马子。
柳暗花明
但是想到了上午利用过的Certutil可以进行编码解码
这样就没有特殊字符了。
完全没问题。
刚开始一点一点追加,发现下面的会写进去两行
看了一下就最后有一个+号,没有特殊字符。
全部直接写进去。
然后decode进行解码,完美。

访问但是是500.不过确很开心,因为确实写上来。
接下来解决为啥500就可以了。
type 123.jsp
查看一下。
发现最后decode的时候,少了一个>
本地测试,是没有这个问题的。可能是目标一次性字符长度的问题。
这里很简单了。
追加一下就可以了。
连接成功
验证
文末福利:
《安卓Frida逆向与协议分析》
本书翔实地介绍流行的Frida工具在安卓逆向工程中的应用,内容包括:如何安装和使用Frida、基本环境的搭建、Frida-tools、Frida脚本、Frida API、批量自动化Trace和分析、RPC远程方法调用、在无须逆向算法具体实现的情况下对Frida工具的调用,并提供了大量App逆向与协议分析案例,书中还介绍了更加稳定的Xposed框架的使用方法,以及从安卓源码开始定制属于自己的抓包沙箱,打造无法被绕过的抓包环境等内容。
本书案例丰富,注重实操,适合安卓应用安全工程师、安卓逆向分析工程师、爬虫工程师以及大数据采集和分析工程师使用。
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。
福利时间:7月25日-7月31日
2. 活动规则:
① 扫描下方二维码参与抽奖或公众号后台回复安卓逆向即可参与;
② 必要条件:转发本文到朋友圈,抽奖前不可删除;
③ 开奖结束后,请中奖小伙伴及时将中奖信息和朋友圈转发记录发送到公众号后台联系,超过24小时未领取的视为自动放弃哈!!!
未满足②条件但被抽中,则获奖资格会被取消哦
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。
社区精选 | 搭建前端监控,采集用户行为的 N 种姿势
采集交流 • 优采云 发表了文章 • 0 个评论 • 130 次浏览 • 2022-07-27 21:31
今天为各位带来的是社区作者杨成功的文章,在这篇文章中他为大家介绍了搭建前端监控,采集用户行为的 N 种姿势。
让我们一起来了解吧~
大家好,我是杨成功。
上一篇我们详细介绍了前端如何采集异常数据。采集异常数据是为了随时监测线上项目的运行情况,发现问题及时修复。在很多场景下,除了异常监控有用,收集用户的行为数据同样有意义。
怎么定义行为数据?顾名思义,就是用户在使用产品过程中产生的行为轨迹。比如去过哪几个页面,点过哪几个按钮,甚至在某个页面停留了多长时间,某个按钮点击了多少次,如果有需求都可以记录下来。
但是记录行为数据是一个和业务紧密关联的事情,不可能把每个用户每一步操作都极其详细的记录下来,这样会产生极其庞大的数据,很显然不现实。
合理的做法是,根据产品的实际情况评估,哪个模块哪个按钮需要重点记录,则可以采集的详细一些;哪些模块不需要重点关注,则简单记录一下基本信息。
根据这个逻辑,我们可以把行为数据分为两类:
通用数据特定数据
下面分别介绍这两类数据该如何收集。
通用数据
在一个产品中,用户最基本的行为就是切换页面。用户使用了哪些功能,也能从切换页面中体现出来。因此通用数据一般是在页面切换时产生,表示某个用户访问了某个页面。
页面切换对应到前端就是路由切换,可以通过监听路由变化来拿到新页面的数据。Vue 在全局路由守卫中监听路由变化,任意路由切换都能执行这里的回调函数。
// Vue3 路由写法<br />const router = createRouter({ ... })<br />router.beforeEach(to => {<br /> // to 代表新页面的路由对象<br /> recordBehaviors(to)<br />})<br />
React 在组件的 useEffect 中实现相同的功能。不过要注意一点,监听所有路由变化,则需要所有路由都经过这个组件,监听才有效果。具体的方法是配置路由时加*配置:
import HomePage from '@/pages/Home'<br />,<br />
然后在这个组件的的 useEffect 中监听路由变化:
// HomePage.jsx<br />const { pathname } = useLocation();<br />useEffect(() => {<br /> // 路由切换这个函数触发<br /> recordBehaviors(pathname);<br />}, [pathname]);<br />
上面代码中,在路由切换时都调用了recordBehaviors()方法并传入了参数。
Vue 传的是一个路由对象,React 传的是路由地址,接下来就可以在这个函数内收集数据了。
明确了在哪里收集数据,我们还要知道收集哪些数据。收集行为数据最基本的字段如下:
上面的字段中,应用标识、环境、版本号统称应用字段,用于标志数据的来源。其他字段主要分为用户,页面,时间三类,通过这三类数据就可以简单的判断出一件事:谁到过哪个页面,并停留了多长时间。
应用字段的配置和获取方式我们在上一节中讲过,就不做多余介绍了,获取字段的方式都是通用的。
下面介绍其他的几类数据如何获取。
获取用户信息
现代前端应用存储用户信息的方式基本都是一样的,localStorage 存一份,状态管理里存一份。因此获取用户信息从这两处的任意一处获得即可。这里简单介绍下如何从状态管理中获取。
最简单的方法,在函数recordBehaviors()所处的 js 文件中,直接导入用户状态:
// 从状态管理里中导出用户数据<br />import { UserStore } from '@/stores';<br />let { user_id, user_name } = UserStore;<br />
这里的@/stores 指向我项目中的文件 src/stores/index.ts,表示状态管理的入口文件,使用时替换成自己项目的实际位置。实际情况中还会有用户数据为空的问题,这里需要单独处理一下,方便我们在后续的数据查看中能看出分别:
import { UserStore } from '@/stores';<br /><br />// 收集行为函数<br />const recordBehaviors = ()=> {<br /> let report_date = {<br /> ...<br /> }<br /> if(UserStore) {<br /> let { user_id, user_name} = UserStore<br /> report_date.user_id = user_id || 0<br /> report_date.user_name = user_name || '未命名'<br /> } else {<br /> report_date.user_id = user_id || -1<br /> report_date.user_name = user_name || '未获取'<br /> }<br />}<br />
上面代码中,首先判断了状态管理中是否有用户数据,如果有则获取,没有则指定默认值。这里指定默认值的细节要注意,不是随便指定的,比如 user_id 的默认值有如下意义:
用户数据是经常容易出错的地方,因为涉及到登录状态和权限等复杂问题。指定了上述默认值后,就可以从收集到的行为数据中判断出某个页面用户状态是否正常。
获取页面信息
前面我们在监听路由变化的地方调用了 recordBehaviors 函数并传入了参数,页面信息可以从参数中拿到,我们先看在 Vue 中怎么获取:
// 路由配置<br />{<br /> path: '/test',<br /> meta: {<br /> title: '测试页面'<br /> },<br /> component: () => import('@/views/test/Index.vue')<br />}<br /><br />// 获取配置<br />const recordBehaviors = (to)=> {<br /> let page_route = to.path<br /> let page_title = to.meta.title<br />}<br />
Vue 中比较简单,可以直接从参数中拿到页面数据。相比之下,React 的参数只是一个路由地址,想拿到页面名称还需要做单独处理。
一般在设计权限时,我们会在服务端会维护一套路由数据,包含路由地址和名称。路由数据在登录后获取,存在状态管理中,那么有了 pathname 就可以从路由数据中找到对应的路由名称。
// React 中<br />import { RouteStore } from '@/stores';<br /><br />const recordBehaviors = (pathname) => {<br /> let { routers } = RouteStore; // 取出路由数据<br /> let route = routers.find((row) => (row.path = pathname));<br /> if (route) {<br /> let page_route = route.path;<br /> let page_title = route.title;<br /> }<br />};<br />
这样,页面信息的 page_route、page_title 两个字段也拿到了。
设置时间
行为数据中用两个字段start_at、end_at分别表示用户进入页面和离开页面的时间。这两个字段非常重要,我们在后续使用数据的时候可以判断出很多信息,比如:
还有很多信息,都能根据这两个时间字段判断。开始时间很好办,函数触发时直接获取当前时间:
<br />var start_at = new Date();<br />
结束时间这里需要考虑的情况比较多。首先要确定数据什么时候上报?用户进入页面后上报,还是离开页面时上报?
如果进入页面时上报,可以保证行为数据一定会被记录,不会丢失,但此时 end_at 字段必然为空。这样的话,就需要在离开页面时再调接口,将这条记录的 end_time 更新,这种方式的实现比较麻烦一些:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> http.post('/behaviors/insert', report_date).then(res=> {<br /> let id = res.id // 数据 id<br /> localStorage.setItem('CURRENT_BEHAVIOR_ID', id)<br /> })<br />}<br /><br />// 离开页面时调用:<br />const updateBehaviors = ()=> {<br /> let id = localStorage.getItem('CURRENT_BEHAVIOR_ID')<br /> let end_at = new Date()<br /> http.post('/behaviors/update/'+id, end_at) // 根据 id 更新结束时间<br /> localStorage.removeItem('CURRENT_BEHAVIOR_ID')<br />}<br />
上面代码中,进入页面先上报数据,并保存下 id,离开页面再根据 id 更新这条数据的结束时间。
如果在离开页面时上报,那么就要保证离开页面前上报接口已经触发,否则会导致数据丢失。在满足这个前提条件下,上报逻辑会变成这样:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> localStorage.setItem('CURRENT_BEHAVIOR', JSON.stringify(report_date));<br />}<br /><br />// 离开页面时调用<br />const reportBehaviors = () => {<br /> let end_at = new Date()<br /> let report_str = localStorage.getItem('CURRENT_BEHAVIOR')<br /> if(report_str) {<br /> let report_date = JSON.parse(report_str)<br /> report_date.end_at = end_at<br /> http.post('/behaviors/insert', report_date)<br /> } else {<br /> console.log('无行为数据')<br /> }<br />}<br />
对比一下这两种方案,第一种的弊端是接口需要调两次,这会使接口请求量倍增。第二种方案只调用一次,但是需要特别注意可靠性处理,总体来说第二种方案更好些。
特定数据
除了通用数据,大部分情况我们还要在具体的页面中收集某些特定的行为。比如某个关键的按钮有没有点击,点了多少次;或者某个关键区域用户有没有看到,看到(曝光)了多少次等等。
收集数据还有一个更专业的叫法 ———— 埋点。直观理解是,哪里需要上报数据,就埋一个上报函数进去。
通用数据针对所有页面自动收集,特定数据就需要根据每个页面的实际需求手动添加。以一个按钮为例:
点击;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
上面代码中,我们想记录这个按钮的点击情况,所以做了一个简单的埋点 ———— 在按钮点击事件中调用 repoerEvents()方法,这个方法内部会收集数据并上报。
这是最原始的埋点方式,直接将上报方法放到事件函数中。repoerEvents() 方法接收一个事件对象参数,在参数中获取需要上报的事件数据。
特定数据与通用数据的许多字段是一样的,收集特定数据需要的基本字段如下:
这些基本字段中,前 7 个字段与前面通用数据的获取完全一样,这里就不赘述了。实际上特定数据需要获取的专有字段只有 3 个:
这三个字段也非常容易获取。event_type 表示事件触发的类型,比如点击、滚动、拖动等,可以在事件对象中拿到。action_tag 和 action_label 是必须指定的属性,表示本次埋点的标识和文字描述,用于在后续的数据处理时方便查阅和统计。
了解了采集特定数据是怎么回事,接下来我们用代码实现。
手动埋点上报
假设要为登录按钮做埋点,按照上面的数据采集方式,我们书写代码如下:
<br /> 登录<br />;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
代码中,我们通过元素的自定义属性传递了 tag 和 label 两个标识,用于在上报函数中获取。
上报函数 repoerEvents() 代码逻辑如下:
// 埋点上报函数<br />const repoerEvents = (e)=> {<br /> let report_date = {...}<br /> let { tag, label } = e.target.dataset<br /> if(!tag || !label) {<br /> return new Error('上报元素属性缺失')<br /> }<br /> report_date.event_type = e.type<br /> report_date.action_tag = tag<br /> report_date.action_label = label<br /><br /> // 上报数据<br /> http.post('/events/insert', report_date)<br />}<br />
这样就实现了一个基本的特定数据埋点上报功能。
全局自动上报
现在我们回过头来梳理一下这个上报流程,虽然基本功能实现了,但是还有些不合理之处,比如:
首先我们的埋点方式是基于事件的,也就是说,不管元素本身是否需要事件处理,我们都要给他加上,并在函数内部调用 repoerEvents() 方法。如果一个项目需要埋点的地方非常多,这种方式的接入成本就会非常高。
参考之前做异常监控的逻辑,我们换一个思路:能否全局监听事件自动上报呢?
思考一下,如果要做全局监听事件,那么只能监听需要埋点的元素的事件。那么如何判断哪些元素需要埋点呢?
上面我们为埋点的元素指定了data-tag和data-label两个自定义属性,那是不是根据这两个自定义属性判断就可以?我们来试验一下:
window.addEventListener('click', (event) => {<br /> let { tag, label, trigger } = event.target.dataset;<br /> if (tag && label && trigger == 'click') {<br /> // 说明该元素需要埋点<br /> repoerEvents(event);<br /> }<br />});<br />
上面代码还多判断了一个自定义属性 dataset.trigger,表示元素在哪种事件触发时需要上报。全局监听事件需要这个标识,这样可避免事件冲突。
添加全局监听后,收集某个元素的特定数据就简单了,方法如下:
<br /> 保存<br /><br />
试验证明,上述全局处理的方式是可行的,这样的话就不需要在每一个元素上添加或修改事件处理函数了,只需要在元素中添加三个自定义属性 data-tag,data-label,data-trigger 就能自动实现数据埋点上报。
组件上报
上面全局监听事件上报的方式已经比手动埋点高效了许多,现在我们再换一个场景。
一般情况下当埋点功能成熟之后,会封装成一个 SDK 供其他项目使用。如果我们将采集数据按照 SDK 的思路实现,让开发者在全局监听事件,是不是一个好的方式呢?
显然是不太友好的。如果是一个 SDK,那么最好的方式是将所有内容聚合成一个组件,在组件内实现上报的所有功能,而不是让使用者在项目中添加监听事件。
封装组件的话,那么组件的功能最好是将要添加埋点的元素包裹,这样自定义元素也就不需要指定了,而转为组件的属性,然后在组件内实现事件监听。
以 React 为例,我们看一下如何将上面的采集功能封装为组件:
import { useEffect, useRef } from 'react';<br /><br />const CusReport = (props) => {<br /> const dom = useRef(null);<br /> const handelEvent = () => {<br /> console.log(props); // {tag:xx, label:xx, trigger:xx}<br /> repoerEvents(props);<br /> };<br /> useEffect(() => {<br /> if (dom.current instanceof HTMLElement) {<br /> dom.current.addEventListener(props.trigger, handelEvent);<br /> }<br /> }, []);<br /> return (<br /> <br /> {props.children}<br /> <br /> );<br />};<br /><br />export default CusReport;<br />
组件使用方式如下:
<br /> 测试<br /><br />
这样就比较优雅了,不需要修改目标元素,只要把组件包裹在目标元素之外即可。
总结
本文介绍了搭建前端监控如何采集行为数据,将数据分为通用数据和特定数据两个大类分别处理。同时也介绍了多种上报数据的方式,不同的场景可以选择不同的方式。
其中的数据部分只介绍了实现功能的基础字段,实际情况中可以根据自己的业务需求添加。
许多小伙伴留言这套前端监控能否开源,肯定是要开源的,不过内容比较多我还在做,等到基本完善了我会发一个版本,感谢小伙伴们的关注。
SegmentFault 思否社区小编说
自 2022-07-01 起 SegmentFault 思否公众号改版啦!之后将陆续推出新的栏目和大家见面!(请拭目以待呀~❤)
在「社区精选」栏目中,我们将为广大开发者推荐来自 SegmentFault 思否开发者社区的优质技术文章,这些文章全部出自社区中充满智慧的技术创作者哦!
希望通过这一栏目,大家可以共同学习技术干货,GET 新技能和各种花式技术小 Tips。
欢迎越来越多的开发者加入创作者的行列,我们将持续甄选出社区中优质的内容推介给更多人,让闪闪发光的技术创作者们走到聚光灯下,被更多人认识。
「社区精选」投稿邮箱:
投稿请附上社区文章地址 查看全部
社区精选 | 搭建前端监控,采集用户行为的 N 种姿势
今天为各位带来的是社区作者杨成功的文章,在这篇文章中他为大家介绍了搭建前端监控,采集用户行为的 N 种姿势。
让我们一起来了解吧~
大家好,我是杨成功。
上一篇我们详细介绍了前端如何采集异常数据。采集异常数据是为了随时监测线上项目的运行情况,发现问题及时修复。在很多场景下,除了异常监控有用,收集用户的行为数据同样有意义。
怎么定义行为数据?顾名思义,就是用户在使用产品过程中产生的行为轨迹。比如去过哪几个页面,点过哪几个按钮,甚至在某个页面停留了多长时间,某个按钮点击了多少次,如果有需求都可以记录下来。
但是记录行为数据是一个和业务紧密关联的事情,不可能把每个用户每一步操作都极其详细的记录下来,这样会产生极其庞大的数据,很显然不现实。
合理的做法是,根据产品的实际情况评估,哪个模块哪个按钮需要重点记录,则可以采集的详细一些;哪些模块不需要重点关注,则简单记录一下基本信息。
根据这个逻辑,我们可以把行为数据分为两类:
通用数据特定数据
下面分别介绍这两类数据该如何收集。
通用数据
在一个产品中,用户最基本的行为就是切换页面。用户使用了哪些功能,也能从切换页面中体现出来。因此通用数据一般是在页面切换时产生,表示某个用户访问了某个页面。
页面切换对应到前端就是路由切换,可以通过监听路由变化来拿到新页面的数据。Vue 在全局路由守卫中监听路由变化,任意路由切换都能执行这里的回调函数。
// Vue3 路由写法<br />const router = createRouter({ ... })<br />router.beforeEach(to => {<br /> // to 代表新页面的路由对象<br /> recordBehaviors(to)<br />})<br />
React 在组件的 useEffect 中实现相同的功能。不过要注意一点,监听所有路由变化,则需要所有路由都经过这个组件,监听才有效果。具体的方法是配置路由时加*配置:
import HomePage from '@/pages/Home'<br />,<br />
然后在这个组件的的 useEffect 中监听路由变化:
// HomePage.jsx<br />const { pathname } = useLocation();<br />useEffect(() => {<br /> // 路由切换这个函数触发<br /> recordBehaviors(pathname);<br />}, [pathname]);<br />
上面代码中,在路由切换时都调用了recordBehaviors()方法并传入了参数。
Vue 传的是一个路由对象,React 传的是路由地址,接下来就可以在这个函数内收集数据了。
明确了在哪里收集数据,我们还要知道收集哪些数据。收集行为数据最基本的字段如下:
上面的字段中,应用标识、环境、版本号统称应用字段,用于标志数据的来源。其他字段主要分为用户,页面,时间三类,通过这三类数据就可以简单的判断出一件事:谁到过哪个页面,并停留了多长时间。
应用字段的配置和获取方式我们在上一节中讲过,就不做多余介绍了,获取字段的方式都是通用的。
下面介绍其他的几类数据如何获取。
获取用户信息
现代前端应用存储用户信息的方式基本都是一样的,localStorage 存一份,状态管理里存一份。因此获取用户信息从这两处的任意一处获得即可。这里简单介绍下如何从状态管理中获取。
最简单的方法,在函数recordBehaviors()所处的 js 文件中,直接导入用户状态:
// 从状态管理里中导出用户数据<br />import { UserStore } from '@/stores';<br />let { user_id, user_name } = UserStore;<br />
这里的@/stores 指向我项目中的文件 src/stores/index.ts,表示状态管理的入口文件,使用时替换成自己项目的实际位置。实际情况中还会有用户数据为空的问题,这里需要单独处理一下,方便我们在后续的数据查看中能看出分别:
import { UserStore } from '@/stores';<br /><br />// 收集行为函数<br />const recordBehaviors = ()=> {<br /> let report_date = {<br /> ...<br /> }<br /> if(UserStore) {<br /> let { user_id, user_name} = UserStore<br /> report_date.user_id = user_id || 0<br /> report_date.user_name = user_name || '未命名'<br /> } else {<br /> report_date.user_id = user_id || -1<br /> report_date.user_name = user_name || '未获取'<br /> }<br />}<br />
上面代码中,首先判断了状态管理中是否有用户数据,如果有则获取,没有则指定默认值。这里指定默认值的细节要注意,不是随便指定的,比如 user_id 的默认值有如下意义:
用户数据是经常容易出错的地方,因为涉及到登录状态和权限等复杂问题。指定了上述默认值后,就可以从收集到的行为数据中判断出某个页面用户状态是否正常。

获取页面信息
前面我们在监听路由变化的地方调用了 recordBehaviors 函数并传入了参数,页面信息可以从参数中拿到,我们先看在 Vue 中怎么获取:
// 路由配置<br />{<br /> path: '/test',<br /> meta: {<br /> title: '测试页面'<br /> },<br /> component: () => import('@/views/test/Index.vue')<br />}<br /><br />// 获取配置<br />const recordBehaviors = (to)=> {<br /> let page_route = to.path<br /> let page_title = to.meta.title<br />}<br />
Vue 中比较简单,可以直接从参数中拿到页面数据。相比之下,React 的参数只是一个路由地址,想拿到页面名称还需要做单独处理。
一般在设计权限时,我们会在服务端会维护一套路由数据,包含路由地址和名称。路由数据在登录后获取,存在状态管理中,那么有了 pathname 就可以从路由数据中找到对应的路由名称。
// React 中<br />import { RouteStore } from '@/stores';<br /><br />const recordBehaviors = (pathname) => {<br /> let { routers } = RouteStore; // 取出路由数据<br /> let route = routers.find((row) => (row.path = pathname));<br /> if (route) {<br /> let page_route = route.path;<br /> let page_title = route.title;<br /> }<br />};<br />
这样,页面信息的 page_route、page_title 两个字段也拿到了。
设置时间
行为数据中用两个字段start_at、end_at分别表示用户进入页面和离开页面的时间。这两个字段非常重要,我们在后续使用数据的时候可以判断出很多信息,比如:
还有很多信息,都能根据这两个时间字段判断。开始时间很好办,函数触发时直接获取当前时间:
<br />var start_at = new Date();<br />
结束时间这里需要考虑的情况比较多。首先要确定数据什么时候上报?用户进入页面后上报,还是离开页面时上报?
如果进入页面时上报,可以保证行为数据一定会被记录,不会丢失,但此时 end_at 字段必然为空。这样的话,就需要在离开页面时再调接口,将这条记录的 end_time 更新,这种方式的实现比较麻烦一些:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> http.post('/behaviors/insert', report_date).then(res=> {<br /> let id = res.id // 数据 id<br /> localStorage.setItem('CURRENT_BEHAVIOR_ID', id)<br /> })<br />}<br /><br />// 离开页面时调用:<br />const updateBehaviors = ()=> {<br /> let id = localStorage.getItem('CURRENT_BEHAVIOR_ID')<br /> let end_at = new Date()<br /> http.post('/behaviors/update/'+id, end_at) // 根据 id 更新结束时间<br /> localStorage.removeItem('CURRENT_BEHAVIOR_ID')<br />}<br />
上面代码中,进入页面先上报数据,并保存下 id,离开页面再根据 id 更新这条数据的结束时间。
如果在离开页面时上报,那么就要保证离开页面前上报接口已经触发,否则会导致数据丢失。在满足这个前提条件下,上报逻辑会变成这样:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> localStorage.setItem('CURRENT_BEHAVIOR', JSON.stringify(report_date));<br />}<br /><br />// 离开页面时调用<br />const reportBehaviors = () => {<br /> let end_at = new Date()<br /> let report_str = localStorage.getItem('CURRENT_BEHAVIOR')<br /> if(report_str) {<br /> let report_date = JSON.parse(report_str)<br /> report_date.end_at = end_at<br /> http.post('/behaviors/insert', report_date)<br /> } else {<br /> console.log('无行为数据')<br /> }<br />}<br />
对比一下这两种方案,第一种的弊端是接口需要调两次,这会使接口请求量倍增。第二种方案只调用一次,但是需要特别注意可靠性处理,总体来说第二种方案更好些。
特定数据
除了通用数据,大部分情况我们还要在具体的页面中收集某些特定的行为。比如某个关键的按钮有没有点击,点了多少次;或者某个关键区域用户有没有看到,看到(曝光)了多少次等等。
收集数据还有一个更专业的叫法 ———— 埋点。直观理解是,哪里需要上报数据,就埋一个上报函数进去。
通用数据针对所有页面自动收集,特定数据就需要根据每个页面的实际需求手动添加。以一个按钮为例:
点击;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
上面代码中,我们想记录这个按钮的点击情况,所以做了一个简单的埋点 ———— 在按钮点击事件中调用 repoerEvents()方法,这个方法内部会收集数据并上报。
这是最原始的埋点方式,直接将上报方法放到事件函数中。repoerEvents() 方法接收一个事件对象参数,在参数中获取需要上报的事件数据。
特定数据与通用数据的许多字段是一样的,收集特定数据需要的基本字段如下:
这些基本字段中,前 7 个字段与前面通用数据的获取完全一样,这里就不赘述了。实际上特定数据需要获取的专有字段只有 3 个:
这三个字段也非常容易获取。event_type 表示事件触发的类型,比如点击、滚动、拖动等,可以在事件对象中拿到。action_tag 和 action_label 是必须指定的属性,表示本次埋点的标识和文字描述,用于在后续的数据处理时方便查阅和统计。
了解了采集特定数据是怎么回事,接下来我们用代码实现。
手动埋点上报
假设要为登录按钮做埋点,按照上面的数据采集方式,我们书写代码如下:
<br /> 登录<br />;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
代码中,我们通过元素的自定义属性传递了 tag 和 label 两个标识,用于在上报函数中获取。
上报函数 repoerEvents() 代码逻辑如下:

// 埋点上报函数<br />const repoerEvents = (e)=> {<br /> let report_date = {...}<br /> let { tag, label } = e.target.dataset<br /> if(!tag || !label) {<br /> return new Error('上报元素属性缺失')<br /> }<br /> report_date.event_type = e.type<br /> report_date.action_tag = tag<br /> report_date.action_label = label<br /><br /> // 上报数据<br /> http.post('/events/insert', report_date)<br />}<br />
这样就实现了一个基本的特定数据埋点上报功能。
全局自动上报
现在我们回过头来梳理一下这个上报流程,虽然基本功能实现了,但是还有些不合理之处,比如:
首先我们的埋点方式是基于事件的,也就是说,不管元素本身是否需要事件处理,我们都要给他加上,并在函数内部调用 repoerEvents() 方法。如果一个项目需要埋点的地方非常多,这种方式的接入成本就会非常高。
参考之前做异常监控的逻辑,我们换一个思路:能否全局监听事件自动上报呢?
思考一下,如果要做全局监听事件,那么只能监听需要埋点的元素的事件。那么如何判断哪些元素需要埋点呢?
上面我们为埋点的元素指定了data-tag和data-label两个自定义属性,那是不是根据这两个自定义属性判断就可以?我们来试验一下:
window.addEventListener('click', (event) => {<br /> let { tag, label, trigger } = event.target.dataset;<br /> if (tag && label && trigger == 'click') {<br /> // 说明该元素需要埋点<br /> repoerEvents(event);<br /> }<br />});<br />
上面代码还多判断了一个自定义属性 dataset.trigger,表示元素在哪种事件触发时需要上报。全局监听事件需要这个标识,这样可避免事件冲突。
添加全局监听后,收集某个元素的特定数据就简单了,方法如下:
<br /> 保存<br /><br />
试验证明,上述全局处理的方式是可行的,这样的话就不需要在每一个元素上添加或修改事件处理函数了,只需要在元素中添加三个自定义属性 data-tag,data-label,data-trigger 就能自动实现数据埋点上报。
组件上报
上面全局监听事件上报的方式已经比手动埋点高效了许多,现在我们再换一个场景。
一般情况下当埋点功能成熟之后,会封装成一个 SDK 供其他项目使用。如果我们将采集数据按照 SDK 的思路实现,让开发者在全局监听事件,是不是一个好的方式呢?
显然是不太友好的。如果是一个 SDK,那么最好的方式是将所有内容聚合成一个组件,在组件内实现上报的所有功能,而不是让使用者在项目中添加监听事件。
封装组件的话,那么组件的功能最好是将要添加埋点的元素包裹,这样自定义元素也就不需要指定了,而转为组件的属性,然后在组件内实现事件监听。
以 React 为例,我们看一下如何将上面的采集功能封装为组件:
import { useEffect, useRef } from 'react';<br /><br />const CusReport = (props) => {<br /> const dom = useRef(null);<br /> const handelEvent = () => {<br /> console.log(props); // {tag:xx, label:xx, trigger:xx}<br /> repoerEvents(props);<br /> };<br /> useEffect(() => {<br /> if (dom.current instanceof HTMLElement) {<br /> dom.current.addEventListener(props.trigger, handelEvent);<br /> }<br /> }, []);<br /> return (<br /> <br /> {props.children}<br /> <br /> );<br />};<br /><br />export default CusReport;<br />
组件使用方式如下:
<br /> 测试<br /><br />
这样就比较优雅了,不需要修改目标元素,只要把组件包裹在目标元素之外即可。
总结
本文介绍了搭建前端监控如何采集行为数据,将数据分为通用数据和特定数据两个大类分别处理。同时也介绍了多种上报数据的方式,不同的场景可以选择不同的方式。
其中的数据部分只介绍了实现功能的基础字段,实际情况中可以根据自己的业务需求添加。
许多小伙伴留言这套前端监控能否开源,肯定是要开源的,不过内容比较多我还在做,等到基本完善了我会发一个版本,感谢小伙伴们的关注。
SegmentFault 思否社区小编说
自 2022-07-01 起 SegmentFault 思否公众号改版啦!之后将陆续推出新的栏目和大家见面!(请拭目以待呀~❤)
在「社区精选」栏目中,我们将为广大开发者推荐来自 SegmentFault 思否开发者社区的优质技术文章,这些文章全部出自社区中充满智慧的技术创作者哦!
希望通过这一栏目,大家可以共同学习技术干货,GET 新技能和各种花式技术小 Tips。
欢迎越来越多的开发者加入创作者的行列,我们将持续甄选出社区中优质的内容推介给更多人,让闪闪发光的技术创作者们走到聚光灯下,被更多人认识。
「社区精选」投稿邮箱:
投稿请附上社区文章地址
实战 | SRC信息收集思路总结
采集交流 • 优采云 发表了文章 • 0 个评论 • 72 次浏览 • 2022-06-18 20:11
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。以下均为小弟拙见,大佬勿喷。
0x01 确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可与从中选择自己感兴趣的SRC。
0x02 确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
0x03 子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
0x04 系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
0x05 框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
0x06 非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
0x07 端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
0x08 目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
0x09 JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
0xA小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
0xB总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,以上内容均为小弟拙见,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
0XC 推荐一个网站
有SRC的厂商列表,可以自己去专属的SRC提交漏洞
查看全部
实战 | SRC信息收集思路总结
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。以下均为小弟拙见,大佬勿喷。
0x01 确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可与从中选择自己感兴趣的SRC。
0x02 确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
0x03 子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
0x04 系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
0x05 框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
0x06 非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
0x07 端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
0x08 目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
0x09 JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
0xA小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
0xB总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,以上内容均为小弟拙见,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
0XC 推荐一个网站
有SRC的厂商列表,可以自己去专属的SRC提交漏洞
SRC信息收集思路分享,很有用!
采集交流 • 优采云 发表了文章 • 0 个评论 • 77 次浏览 • 2022-06-18 15:01
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。
确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。
所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。关联阅读:
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可以从中选择自己感兴趣的SRC。
确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。
里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。
下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass(现在无法使用,勿下载)
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。
那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。
经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。
说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?
不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。
例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
https://github.com/Threezh1/JSFinder
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。
个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。
来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。
下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。
小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。
也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。
所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
声明:本公众号所分享内容仅用于网安爱好者之间的技术讨论,禁止用于违法途径,所有渗透都需获取授权!否则需自行承担,本公众号及原作者不承担相应的后果.
@学习更多渗透技能!体验靶场实战练习
<p style="outline: 0px;letter-spacing: 0.544px;">(hack视频资料及工具)<br style="outline: 0px;" />
</p>
(部分展示)
往期推荐
看到这里了,点个“赞”、“再看”吧 查看全部
SRC信息收集思路分享,很有用!
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。
确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。
所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。关联阅读:
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可以从中选择自己感兴趣的SRC。
确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。
里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。
下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass(现在无法使用,勿下载)
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。
那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。
经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。
说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?
不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。
例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
https://github.com/Threezh1/JSFinder
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。
个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。
来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。
下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。
小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。
也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。
所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
声明:本公众号所分享内容仅用于网安爱好者之间的技术讨论,禁止用于违法途径,所有渗透都需获取授权!否则需自行承担,本公众号及原作者不承担相应的后果.
@学习更多渗透技能!体验靶场实战练习
<p style="outline: 0px;letter-spacing: 0.544px;">(hack视频资料及工具)<br style="outline: 0px;" />
(部分展示)
往期推荐
看到这里了,点个“赞”、“再看”吧
文章采集excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~
采集交流 • 优采云 发表了文章 • 0 个评论 • 129 次浏览 • 2022-06-09 14:19
文章采集调用excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~自动刷新banner、喜欢题目后推荐的手游、相应游戏的收入大幅增长~欢迎关注公众号并与本人交流~【长按扫码加入游戏帝国数据交流群】群名称:数据魔方收集题目:哪些网站比百度更懂中国互联网?将通过采集不同维度的数据,去分析中国互联网的发展和中国互联网游戏的发展趋势。
这次采集的数据集是智能问答领域的相关数据。数据集量为22631条,包含512个样本。每条样本包含30个问题,554个标签。这次的目标是分析一下2010年至今国内大多数站点上的语音通话,也包括大量没有开放数据的,例如通话能力不达标准的站点。预计耗时3-5天。数据集说明数据样本:本数据集已经对问题,标签以及语音频率进行了pre处理,每条问题后面使用逗号间隔隔开。
读取数据时需要选择以weka或java版本的python库,手机端的java在数据读取阶段发生了错误,大约耗时30秒。我尝试在kaggle的googleanalytics中数据导入,然后再使用kaggle自带的googleloggersspark套件导入数据集,但最终结果并不好。代码数据格式:youtu.be/i17alvfog提取方式:以字典格式读取youtu.be/kgcolor_readerkcbxt提取数据的关键字段fname提取数据所在位置:数据读取工具:pipinstallpandaspandasimportpandasaspd#读取获取数据pandasread_csv('数据集.csv',index=true)#将数据集变换成dataframe格式csv_data=pd.read_csv(pandas.dataframe(fname=fname))#读取数据集head=csv_data.head(5)#数据集概览title1=csv_data.title2=csv_data.title3=csv_data.title4=csv_data.title5=csv_data.title6=csv_data.title7=csv_data.title8=csv_data.title9=csv_data.title10=csv_data.title11=csv_data.title12=csv_data.title13=csv_data.title14=csv_data.title15=csv_data.title16=csv_data.title17=csv_data.title18=csv_data.title19=csv_data.title20=csv_data.title21=csv_data.title22=csv_data.title23=csv_data.title24=csv_data.title25=csv_data.title26=csv_data.title27=csv_data.title28=csv_data.title29。 查看全部
文章采集excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~
文章采集调用excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~自动刷新banner、喜欢题目后推荐的手游、相应游戏的收入大幅增长~欢迎关注公众号并与本人交流~【长按扫码加入游戏帝国数据交流群】群名称:数据魔方收集题目:哪些网站比百度更懂中国互联网?将通过采集不同维度的数据,去分析中国互联网的发展和中国互联网游戏的发展趋势。
这次采集的数据集是智能问答领域的相关数据。数据集量为22631条,包含512个样本。每条样本包含30个问题,554个标签。这次的目标是分析一下2010年至今国内大多数站点上的语音通话,也包括大量没有开放数据的,例如通话能力不达标准的站点。预计耗时3-5天。数据集说明数据样本:本数据集已经对问题,标签以及语音频率进行了pre处理,每条问题后面使用逗号间隔隔开。
读取数据时需要选择以weka或java版本的python库,手机端的java在数据读取阶段发生了错误,大约耗时30秒。我尝试在kaggle的googleanalytics中数据导入,然后再使用kaggle自带的googleloggersspark套件导入数据集,但最终结果并不好。代码数据格式:youtu.be/i17alvfog提取方式:以字典格式读取youtu.be/kgcolor_readerkcbxt提取数据的关键字段fname提取数据所在位置:数据读取工具:pipinstallpandaspandasimportpandasaspd#读取获取数据pandasread_csv('数据集.csv',index=true)#将数据集变换成dataframe格式csv_data=pd.read_csv(pandas.dataframe(fname=fname))#读取数据集head=csv_data.head(5)#数据集概览title1=csv_data.title2=csv_data.title3=csv_data.title4=csv_data.title5=csv_data.title6=csv_data.title7=csv_data.title8=csv_data.title9=csv_data.title10=csv_data.title11=csv_data.title12=csv_data.title13=csv_data.title14=csv_data.title15=csv_data.title16=csv_data.title17=csv_data.title18=csv_data.title19=csv_data.title20=csv_data.title21=csv_data.title22=csv_data.title23=csv_data.title24=csv_data.title25=csv_data.title26=csv_data.title27=csv_data.title28=csv_data.title29。
文章采集调用googleapi,估计能免费获取到文章的几篇文章
采集交流 • 优采云 发表了文章 • 0 个评论 • 113 次浏览 • 2022-05-22 16:06
文章采集调用googleapi,估计能免费获取到文章的几篇文章,但注意看好文章!!!推荐小黑胖收集整理的。那个都能看到,只是bug多。要不你弄弄这个,一篇文章都能看到。其他平台应该也行。但能不能一次采集到整个链接,就是另一回事了。需要用到文章收集软件。把需要收集的文章都收集起来,重点是还是一次性收集好后不能再次收集的,在返回收集文章的软件就行。
需要注册一个谷歌账号,
采集云,一键登录,
如果是简单的博客爬虫,可以直接下载相关api文档即可。googleapidocs需要翻墙,很多博客爬虫都可以参考。
googlereader.
freegradleweekly
api,github上有很多开源的githubapis,都能抓googleapi;scrapy,scrapy是scrapy框架开发的,可以抓googleapi;httplib,可以抓googleapi,不用翻墙;用python爬虫先看googleapi,googleapi没有你想象的那么复杂,网上有例子,实践下就知道了。
推荐apifinder。而且目前来说,
googleapidocs
newmail-weeklyextension
apifinder
有使用过,也有之前从网上找到的api,googleapisnewsreader这些,都大同小异。 查看全部
文章采集调用googleapi,估计能免费获取到文章的几篇文章
文章采集调用googleapi,估计能免费获取到文章的几篇文章,但注意看好文章!!!推荐小黑胖收集整理的。那个都能看到,只是bug多。要不你弄弄这个,一篇文章都能看到。其他平台应该也行。但能不能一次采集到整个链接,就是另一回事了。需要用到文章收集软件。把需要收集的文章都收集起来,重点是还是一次性收集好后不能再次收集的,在返回收集文章的软件就行。
需要注册一个谷歌账号,
采集云,一键登录,
如果是简单的博客爬虫,可以直接下载相关api文档即可。googleapidocs需要翻墙,很多博客爬虫都可以参考。
googlereader.
freegradleweekly
api,github上有很多开源的githubapis,都能抓googleapi;scrapy,scrapy是scrapy框架开发的,可以抓googleapi;httplib,可以抓googleapi,不用翻墙;用python爬虫先看googleapi,googleapi没有你想象的那么复杂,网上有例子,实践下就知道了。
推荐apifinder。而且目前来说,
googleapidocs
newmail-weeklyextension
apifinder
有使用过,也有之前从网上找到的api,googleapisnewsreader这些,都大同小异。
【文末赠书】红队攻防之信息收集总结
采集交流 • 优采云 发表了文章 • 0 个评论 • 85 次浏览 • 2022-05-15 14:33
之前也总结过类似的信息收集相关的文章,但是每隔一段时间理解和手法都会有所不同,本文以hvv或授权但仅提供公司名称域名等情况下渗透测试的视角总结一些自己最近做信息收集的流程套路。
信息收集一、初始已知信息
前言中提到的两种情况,一般初始信息只有公司名称、个别官网域名、靶标名称等信息,以此为起点进行信息收集。
二、搜寻根域名
此步骤个人的经验是,面对大公司优先选择工信部备案查询,小公司用搜索引擎做起点,然后几种方式都可以过一遍,查漏补缺,尽量获取最全的信息。大部分公司根域名都不会很多,全部过一遍也不会用掉多少时间。
1.搜索引擎
搜索引擎直接搜索其公司名称,获取其相关根域名
2.天眼查、企查查
从天眼查、企查查等途径,输入公司名,查询其域名以及全资控股子公司的域名
3.工信部备案
工信部备案查询域名/ip地址(需要详细且正确的公司名称,结果也会很全面)
#/Integrated/recordQuery
4.fofa
fofa查询其公司名称,获取相关域名
5.站长之家
使用其icp查询功能查询备案,当我们不知道公司完整名称的时候也可以使用此网站功能使用已知域名查询完整备案公司名称
6.反查域名
用已知的某些ip反查域名
三、子域名
在子域名收集这步本人一般不喜欢爆破的方式,子域名爆破比较依赖字典,字典小就收集不全,字典大就很费时间,所以一般优先在各类解析记录的网站查询。
1.各类网站查询解析记录
以bilibili为例:
类似的网站非常多,这两个都是免费的,但是第二个要注册登录
2.子域名爆破
相关的工具很多,部分扫描器也自带子域名爆破功能或可安装相关插件。
subDomainsBrute
3.fofa、shodan
利用这类工具对域名资产进行查询,如
fofa语法domain=””
4.OneForAll
此工具会集成多种方式搜集子域名,包括dns查询、证书查询等,详情见其项目中的readme
安装
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />3<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />4<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />5<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
git clone https://github.com/shmilylty/OneForAll.git<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />cd OneForAll/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 -m pip install -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --help<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
python3 oneforall.py --target example.com run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --targets ./example.txt run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
四、ip
ip列表不完全来源于域名解析,有一部分ip是直接使用ip地址提供服务的,需要提前收集这部分信息,另一部分是通过域名解析过来的。
1.各类网站查询解析记录
同子域名查询中的操作,但是需要做的是把ip列表导出
2.解析域名
将所有已收集到的子域名通过脚本批量调用dig或nslookup解析ip
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
nslookup xxx.com<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />dig xxx.com @114.114.114.114<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
编写脚本批量调用dig命令,导出结果
或将域名列表放在在线解析网站中,导出其解析结果
这个步骤中需要额外关注cdn的情况,绕过cdn寻找其真实ip,可参考这篇文档
3.c段
将前面已经获得的ip全部整理好,使用脚本进行排序,懒得写脚本也可以使用在线的功能
如ip地址排序计算器
得到排序好的ip,可以先自己判断哪些c段可能属于目标,再进行一些扫描和访问,整理更全面的ip列表。
五、端口
使用masscan、nmap等工具对端口信息进行收集
六、web服务
使用webfinder等工具扫描已整理ip列表的web常用端口,导出形如:port/以及:port/的web服务列表
指纹识别
1.
2.
七、漏扫1.主机扫描
上文整理好的ip列表和域名列表,可以丢入主机扫描相关的扫描器中,如goby、Nessus等
2.web扫描
整理好web服务列表,可以丢入awvs等工具进行扫描,同时可以联动xray批量扫描 查看全部
【文末赠书】红队攻防之信息收集总结
之前也总结过类似的信息收集相关的文章,但是每隔一段时间理解和手法都会有所不同,本文以hvv或授权但仅提供公司名称域名等情况下渗透测试的视角总结一些自己最近做信息收集的流程套路。
信息收集一、初始已知信息
前言中提到的两种情况,一般初始信息只有公司名称、个别官网域名、靶标名称等信息,以此为起点进行信息收集。
二、搜寻根域名
此步骤个人的经验是,面对大公司优先选择工信部备案查询,小公司用搜索引擎做起点,然后几种方式都可以过一遍,查漏补缺,尽量获取最全的信息。大部分公司根域名都不会很多,全部过一遍也不会用掉多少时间。
1.搜索引擎
搜索引擎直接搜索其公司名称,获取其相关根域名
2.天眼查、企查查
从天眼查、企查查等途径,输入公司名,查询其域名以及全资控股子公司的域名
3.工信部备案
工信部备案查询域名/ip地址(需要详细且正确的公司名称,结果也会很全面)
#/Integrated/recordQuery
4.fofa
fofa查询其公司名称,获取相关域名
5.站长之家
使用其icp查询功能查询备案,当我们不知道公司完整名称的时候也可以使用此网站功能使用已知域名查询完整备案公司名称
6.反查域名
用已知的某些ip反查域名
三、子域名
在子域名收集这步本人一般不喜欢爆破的方式,子域名爆破比较依赖字典,字典小就收集不全,字典大就很费时间,所以一般优先在各类解析记录的网站查询。
1.各类网站查询解析记录
以bilibili为例:
类似的网站非常多,这两个都是免费的,但是第二个要注册登录
2.子域名爆破
相关的工具很多,部分扫描器也自带子域名爆破功能或可安装相关插件。
subDomainsBrute
3.fofa、shodan
利用这类工具对域名资产进行查询,如
fofa语法domain=””
4.OneForAll
此工具会集成多种方式搜集子域名,包括dns查询、证书查询等,详情见其项目中的readme
安装
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />3<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />4<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />5<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
git clone https://github.com/shmilylty/OneForAll.git<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />cd OneForAll/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 -m pip install -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --help<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
python3 oneforall.py --target example.com run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --targets ./example.txt run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
四、ip
ip列表不完全来源于域名解析,有一部分ip是直接使用ip地址提供服务的,需要提前收集这部分信息,另一部分是通过域名解析过来的。
1.各类网站查询解析记录
同子域名查询中的操作,但是需要做的是把ip列表导出
2.解析域名
将所有已收集到的子域名通过脚本批量调用dig或nslookup解析ip
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
nslookup xxx.com<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />dig xxx.com @114.114.114.114<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
编写脚本批量调用dig命令,导出结果
或将域名列表放在在线解析网站中,导出其解析结果
这个步骤中需要额外关注cdn的情况,绕过cdn寻找其真实ip,可参考这篇文档
3.c段
将前面已经获得的ip全部整理好,使用脚本进行排序,懒得写脚本也可以使用在线的功能
如ip地址排序计算器
得到排序好的ip,可以先自己判断哪些c段可能属于目标,再进行一些扫描和访问,整理更全面的ip列表。
五、端口
使用masscan、nmap等工具对端口信息进行收集
六、web服务
使用webfinder等工具扫描已整理ip列表的web常用端口,导出形如:port/以及:port/的web服务列表
指纹识别
1.
2.
七、漏扫1.主机扫描
上文整理好的ip列表和域名列表,可以丢入主机扫描相关的扫描器中,如goby、Nessus等
2.web扫描
整理好web服务列表,可以丢入awvs等工具进行扫描,同时可以联动xray批量扫描
一篇文章教会你利用Python网络爬虫实现豆瓣电影采集
采集交流 • 优采云 发表了文章 • 0 个评论 • 199 次浏览 • 2022-05-14 11:03
点击上方“IT共享之家”,进行关注
回复“资料”可获赠Python学习福利
【一、项目背景】
豆瓣电影提供最新的电影介绍及评论包括上映影片的影讯查询及购票服务。可以记录想看、在看和看过的电影电视剧 、顺便打分、写影评。极大地方便了人们的生活。
今天以电视剧(美剧)为例,批量爬取对应的电影,写入csv文档 。用户可以通过评分,更好的选择自己想要的电影。
【二、项目目标】
获取对应的电影名称,评分,详情链接,下载 电影的图片,保存文档。
【三、涉及的库和网站】
1、网址如下:
https://movie.douban.com/j/sea ... rt%3D{}
2、涉及的库:requests、fake_useragent、json、csv
3、软件:PyCharm
【四、项目分析】
1、如何多网页请求?
点击下一页时,每增加一页paged自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
2. 如何获取真正请求的地址?
请求数据时,发现页面上并没有对应数据。其实豆瓣网采用javascript动态加载内容,防止采集。
1)F12右键检查,找到Network,左边菜单Name , 找到第五个数据,点击Preview。
2)点开subjects,可以看到 title 就是对应电影名称。rate就是对应评分。通过js解析subjects字典,找到需要的字段。
3. 如何网页访问?
https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3D60
当点击下一页时,每增加一页page自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
【五、项目实施】
1、我们定义一个class类继承object,然后定义init方法继承self,再定义一个主函数main继承self。导入需要的库和请求网址。
import requests,jsonfrom fake_useragent import UserAgentimport csv<br />class Doban(object): def __init__(self): self.url = "https://movie.douban.com/j/sea ... rt%3D{}"<br /> def main(self): pass<br />if __name__ == '__main__': Siper = Doban() Siper.main()
2、随机产生UserAgent,构造请求头,防止反爬。
for i in range(1, 50): self.headers = { 'User-Agent': ua.random, }
3、发送请求 ,获取响应,页面回调,方便下次请求。
def get_page(self, url): res = requests.get(url=url, headers=self.headers) html = res.content.decode("utf-8") return html
4、json解析页面数据,获取对应的字典。
data = json.loads(html)['subjects'] # print(data[0])
5、for遍历,获取对应的电影名、 评分、下详情页链接。
print(name, goblin_herf) html2 = self.get_page(goblin_herf) # 第二个发生请求 parse_html2 = etree.HTML(html2) r = parse_html2.xpath('//div[@class="entry"]/p/text()')
6、创建csv文件进行写入,定义对应的标题头内容,保存数据 。
# 创建csv文件进行写入 csv_file = open('scr.csv', 'a', encoding='gbk') csv_writer = csv.writer(csv_file) # 写入csv标题头内容 csv_writerr.writerow(['电影', '评分', "详情页"]) #写入数据 csv_writer.writerow([id, rate, urll])
7、图片地址进行请求。定义图片名称,保存文档。
html2 = requests.get(url=urll, headers=self.headers).content dirname = "./图/" + id + ".jpg" with open(dirname, 'wb') as f: f.write(html2) print("%s 【下载成功!!!!】" % id)
8、调用方法,实现功能。
html = self.get_page(url) self.parse_page(html)
9、项目优化:1)设置时间延时。
time.sleep(1.4)
2)定义一个变量u, for遍历,表示爬取的是第几页。(更清晰可观)。
u = 0 self.u += 1;
【六、效果展示】
1、点击绿色小三角运行输入起始页,终止页( 从0页开始 )。
2、将下载成功信息显示在控制台。
3、保存csv文档。
4、电影图片展示。
【七、总结】
1、不建议抓取太多数据,容易对服务器造成负载,浅尝辄止即可。
2、本文章就Python爬取豆瓣网,在应用中出现的难点和重点,以及如何防止反爬,做出了相对于的解决方案。
3、希望通过这个项目,能够帮助了解json解析页面的基本流程,字符串是如何拼接,format函数如何运用。
4、本文基于Python网络爬虫,利用爬虫库,实现豆瓣电影及其图片的获取。实现的时候,总会有各种各样的问题,切勿眼高手低,勤动手,才可以理解的更加深刻。
5、需要本文源码的小伙伴,请在下方公众号后台回复“豆瓣电影”四个字,即可获取。
看完本文有收获?请转发分享给更多的人
IT共享之家 查看全部
一篇文章教会你利用Python网络爬虫实现豆瓣电影采集
点击上方“IT共享之家”,进行关注
回复“资料”可获赠Python学习福利
【一、项目背景】
豆瓣电影提供最新的电影介绍及评论包括上映影片的影讯查询及购票服务。可以记录想看、在看和看过的电影电视剧 、顺便打分、写影评。极大地方便了人们的生活。
今天以电视剧(美剧)为例,批量爬取对应的电影,写入csv文档 。用户可以通过评分,更好的选择自己想要的电影。
【二、项目目标】
获取对应的电影名称,评分,详情链接,下载 电影的图片,保存文档。
【三、涉及的库和网站】
1、网址如下:
https://movie.douban.com/j/sea ... rt%3D{}
2、涉及的库:requests、fake_useragent、json、csv
3、软件:PyCharm
【四、项目分析】
1、如何多网页请求?
点击下一页时,每增加一页paged自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
2. 如何获取真正请求的地址?
请求数据时,发现页面上并没有对应数据。其实豆瓣网采用javascript动态加载内容,防止采集。
1)F12右键检查,找到Network,左边菜单Name , 找到第五个数据,点击Preview。
2)点开subjects,可以看到 title 就是对应电影名称。rate就是对应评分。通过js解析subjects字典,找到需要的字段。
3. 如何网页访问?
https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3D60
当点击下一页时,每增加一页page自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
【五、项目实施】
1、我们定义一个class类继承object,然后定义init方法继承self,再定义一个主函数main继承self。导入需要的库和请求网址。
import requests,jsonfrom fake_useragent import UserAgentimport csv<br />class Doban(object): def __init__(self): self.url = "https://movie.douban.com/j/sea ... rt%3D{}"<br /> def main(self): pass<br />if __name__ == '__main__': Siper = Doban() Siper.main()
2、随机产生UserAgent,构造请求头,防止反爬。
for i in range(1, 50): self.headers = { 'User-Agent': ua.random, }
3、发送请求 ,获取响应,页面回调,方便下次请求。
def get_page(self, url): res = requests.get(url=url, headers=self.headers) html = res.content.decode("utf-8") return html
4、json解析页面数据,获取对应的字典。
data = json.loads(html)['subjects'] # print(data[0])
5、for遍历,获取对应的电影名、 评分、下详情页链接。
print(name, goblin_herf) html2 = self.get_page(goblin_herf) # 第二个发生请求 parse_html2 = etree.HTML(html2) r = parse_html2.xpath('//div[@class="entry"]/p/text()')
6、创建csv文件进行写入,定义对应的标题头内容,保存数据 。
# 创建csv文件进行写入 csv_file = open('scr.csv', 'a', encoding='gbk') csv_writer = csv.writer(csv_file) # 写入csv标题头内容 csv_writerr.writerow(['电影', '评分', "详情页"]) #写入数据 csv_writer.writerow([id, rate, urll])
7、图片地址进行请求。定义图片名称,保存文档。
html2 = requests.get(url=urll, headers=self.headers).content dirname = "./图/" + id + ".jpg" with open(dirname, 'wb') as f: f.write(html2) print("%s 【下载成功!!!!】" % id)
8、调用方法,实现功能。
html = self.get_page(url) self.parse_page(html)
9、项目优化:1)设置时间延时。
time.sleep(1.4)
2)定义一个变量u, for遍历,表示爬取的是第几页。(更清晰可观)。
u = 0 self.u += 1;
【六、效果展示】
1、点击绿色小三角运行输入起始页,终止页( 从0页开始 )。
2、将下载成功信息显示在控制台。
3、保存csv文档。
4、电影图片展示。
【七、总结】
1、不建议抓取太多数据,容易对服务器造成负载,浅尝辄止即可。
2、本文章就Python爬取豆瓣网,在应用中出现的难点和重点,以及如何防止反爬,做出了相对于的解决方案。
3、希望通过这个项目,能够帮助了解json解析页面的基本流程,字符串是如何拼接,format函数如何运用。
4、本文基于Python网络爬虫,利用爬虫库,实现豆瓣电影及其图片的获取。实现的时候,总会有各种各样的问题,切勿眼高手低,勤动手,才可以理解的更加深刻。
5、需要本文源码的小伙伴,请在下方公众号后台回复“豆瓣电影”四个字,即可获取。
看完本文有收获?请转发分享给更多的人
IT共享之家
文章采集调用 好代码和坏代码
采集交流 • 优采云 发表了文章 • 0 个评论 • 127 次浏览 • 2022-05-14 06:28
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
查看全部
文章采集调用 好代码和坏代码
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
V8 中的垃圾收集(GC),图文指南
采集交流 • 优采云 发表了文章 • 0 个评论 • 79 次浏览 • 2022-05-14 06:27
原文标题:Garbage collection in V8, an illustrated guide原文链接:@_lrlna/garbage-collection-in-v8-an-illustrated-guide-d24a952ee3b8
本指南与我迄今为止所写的其他指南都不同,我在里面添加了一些草图。我用草图描绘了垃圾收集(GC)的整个概念以及它是如何在 javascript 中被处理的,更确切地说是在运行 javascript 的引擎中。顺便提一下,这个指南是面向初学者的,不包括 V8 内存管理的各个方面以及 V8 的内部原理。我添加了一些资源,可以帮助你更深入地了解。本指南重点介绍✨javascript✨,对于某些语言而言,垃圾收集是完全不一样的,比如 C 语言.
好的,我们开始吧。
什么是v8?
V8,是一个 JavaScript 的运行时引擎,不要与你最喜爱的番茄汁混淆了,它负责编译并执行你精美的javascript。V8 带有分代垃圾收集器,我将在后文解释。它与 Chrome 一起,而 SpiderMonkey 是 Mozilla 的引擎 Chakra 是微软的。基本上当运行 javascript 时,您需要一个引擎来处理它,而且 V8 是您的选择之一,无论是在浏览器还是在 node.js 环境中。(P.S. V8 是✨ 开源的 ✨。)
什么是垃圾收集?
垃圾收集的重点是通过使用特定的程序来管理内存的使用。诸如 C 之类的语言通常可以直接操作程序中的内存,并在程序的上下文中分配和释放对象。另一方面,ECMAScript 缺少访问内存管理的特定接口(是的,这意味着没有API)。这基本上意味着程序中的所有内存管理权限都被转移到了 V8。
由于我们无法访问无限量的内存,因此垃圾收集器的工作是通过内存中分配的对象来确定它们是否死亡或是活动。那些活着的对象会留在内存中,那些死亡的对象被删除,内存被分配回堆。
什么是堆?堆是非结构化区域,堆中的对象占用分配的内存。这种分配是动态的,因为对象的大小/寿命/数量是未知的,所以需要在运行时分配和释放。
如果我们看一下并发模型,堆直接与调用栈一起工作,因为堆栈中的对象需要进行内存分配。它看起来像这样:
Dead or alive?
如何检查对象的生死,是通过客户机或者程序代码是否可以到达此对象。您可以想到的最容易达到的对象可能是根范围中定义的对象。
一些 C++ 绑定(或客户端上的 Web API)也是根的一部分,因此您可以通过例如 setInterval 直接访问。
可达性(Reachability)还可以这么理解:另一个对象或根是否可以获得它,如果可以的话,该对象所需的内存被保留。
那么我们怎么可以做到垃圾收集呢?(告诉我!告诉我!)
创建新对象或新的“指针”时,V8 会在堆中分配内存。(javascript 没有真正的指针,所以'指针'在技术上只是复制对原始对象的引用)。堆中的不同类型的对象会占用不同的空间,它将被组织成如下:
为了垃圾回收的目的,V8 将堆分为两部分:新生区和老生区。当您执行需要 V8 分配内存的操作时,V8 将在新生区中分配空间。当你继续添加到堆,你最终会耗尽内存,所以 V8 将不得不运行一个 GC 来清理。新创建的对象被分配得很快,并且当对象死亡时被清理(更短和更快的收集)。一旦对象“生存”了一些(确切的说是2个周期)回收扫描周期时,它们被提升到老生区,在一个单独的循环中收集垃圾。
较旧的对象是幸存多于一个垃圾收集扫描的对象,这意味着它们仍然被其他对象引用,并且仍然需要占用该内存。他们通常不引用较年轻的对象,只是引用较旧的对象。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。
sources.js
This guide is crossposted from lrlna’s sketchin guide on github ✨ . 查看全部
V8 中的垃圾收集(GC),图文指南
原文标题:Garbage collection in V8, an illustrated guide原文链接:@_lrlna/garbage-collection-in-v8-an-illustrated-guide-d24a952ee3b8
本指南与我迄今为止所写的其他指南都不同,我在里面添加了一些草图。我用草图描绘了垃圾收集(GC)的整个概念以及它是如何在 javascript 中被处理的,更确切地说是在运行 javascript 的引擎中。顺便提一下,这个指南是面向初学者的,不包括 V8 内存管理的各个方面以及 V8 的内部原理。我添加了一些资源,可以帮助你更深入地了解。本指南重点介绍✨javascript✨,对于某些语言而言,垃圾收集是完全不一样的,比如 C 语言.
好的,我们开始吧。
什么是v8?
V8,是一个 JavaScript 的运行时引擎,不要与你最喜爱的番茄汁混淆了,它负责编译并执行你精美的javascript。V8 带有分代垃圾收集器,我将在后文解释。它与 Chrome 一起,而 SpiderMonkey 是 Mozilla 的引擎 Chakra 是微软的。基本上当运行 javascript 时,您需要一个引擎来处理它,而且 V8 是您的选择之一,无论是在浏览器还是在 node.js 环境中。(P.S. V8 是✨ 开源的 ✨。)
什么是垃圾收集?
垃圾收集的重点是通过使用特定的程序来管理内存的使用。诸如 C 之类的语言通常可以直接操作程序中的内存,并在程序的上下文中分配和释放对象。另一方面,ECMAScript 缺少访问内存管理的特定接口(是的,这意味着没有API)。这基本上意味着程序中的所有内存管理权限都被转移到了 V8。
由于我们无法访问无限量的内存,因此垃圾收集器的工作是通过内存中分配的对象来确定它们是否死亡或是活动。那些活着的对象会留在内存中,那些死亡的对象被删除,内存被分配回堆。
什么是堆?堆是非结构化区域,堆中的对象占用分配的内存。这种分配是动态的,因为对象的大小/寿命/数量是未知的,所以需要在运行时分配和释放。
如果我们看一下并发模型,堆直接与调用栈一起工作,因为堆栈中的对象需要进行内存分配。它看起来像这样:
Dead or alive?
如何检查对象的生死,是通过客户机或者程序代码是否可以到达此对象。您可以想到的最容易达到的对象可能是根范围中定义的对象。
一些 C++ 绑定(或客户端上的 Web API)也是根的一部分,因此您可以通过例如 setInterval 直接访问。
可达性(Reachability)还可以这么理解:另一个对象或根是否可以获得它,如果可以的话,该对象所需的内存被保留。
那么我们怎么可以做到垃圾收集呢?(告诉我!告诉我!)
创建新对象或新的“指针”时,V8 会在堆中分配内存。(javascript 没有真正的指针,所以'指针'在技术上只是复制对原始对象的引用)。堆中的不同类型的对象会占用不同的空间,它将被组织成如下:
为了垃圾回收的目的,V8 将堆分为两部分:新生区和老生区。当您执行需要 V8 分配内存的操作时,V8 将在新生区中分配空间。当你继续添加到堆,你最终会耗尽内存,所以 V8 将不得不运行一个 GC 来清理。新创建的对象被分配得很快,并且当对象死亡时被清理(更短和更快的收集)。一旦对象“生存”了一些(确切的说是2个周期)回收扫描周期时,它们被提升到老生区,在一个单独的循环中收集垃圾。
较旧的对象是幸存多于一个垃圾收集扫描的对象,这意味着它们仍然被其他对象引用,并且仍然需要占用该内存。他们通常不引用较年轻的对象,只是引用较旧的对象。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。
sources.js
This guide is crossposted from lrlna’s sketchin guide on github ✨ .
文章采集调用 好代码和坏代码
采集交流 • 优采云 发表了文章 • 0 个评论 • 139 次浏览 • 2022-05-10 05:21
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
查看全部
文章采集调用 好代码和坏代码
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
Spring Boot 如何监控 SQL 运行情况?
采集交流 • 优采云 发表了文章 • 0 个评论 • 88 次浏览 • 2022-05-09 12:17
关注后回复“进群”,拉你进程序员交流群
今天想和大家聊一聊 Druid 中的监控功能。
Druid 数据库连接池相信很多小伙伴都用过,个人感觉 Druid 是阿里比较成功的开源项目了,不像 Fastjson 那么多槽点,Druid 各方面一直都比较出色,功能齐全,使用也方便,基本的用法就不说了,今天我们来看看 Druid 中的监控功能。
1. 准备工作
首先我们来创建一个 Spring Boot 工程,引入 MyBatis 等,如下:
选一下 MyBatis 和 MySQL 驱动,做一个简单的测试案例。
先来连接一下数据库:
spring.datasource.username=root<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.url=jdbc:mysql:///test05?serverTimezone=Asia/Shanghai<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
创建一个 User 实体类,做一个简单的查询案例,如下:
public class User {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private Integer id;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String username;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String address;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String password;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String email;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> //省略 getter/setter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Mapper<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public interface UserMapper {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> List getUserByUsername(String username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Service<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserService {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserMapper userMapper;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUserByUsername(String username){<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userMapper.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@RestController<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserController {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserService userService;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @GetMapping("/user")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUser(String username) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userService.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
UserMapper.xml 如下:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> select * from user where username=#{username}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
一个很简单的测试,没啥好说的。
这个环境搭建大家随意,如果你已经有持久化的案例了,那就直接看第二小节引入 Druid。
现在这个工程默认的使用的数据库连接池是 HikariDataSource,这是 Spring Boot 中默认的一个数据库连接池,其实这个也还不错。
2. 引入 Druid
接下来我们引入 Druid:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> com.alibaba<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid-spring-boot-starter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 1.2.8<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
注意,Spring Boot 引入的 Druid 是上面这个,这个将来配置监控的时候方便一些。
接下来我们在 application.properties 中配置 WebStatFilter,WebStatFilter 用于采集 web-jdbc 关联监控的数据:
# 启用 WebStatFilter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置拦截规则<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.url-pattern=/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 排除一些不必要的 url,这些 URL 不会涉及到 SQL 查询<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 session 统计功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 缺省 sessionStatMaxCount 是 1000 个,我们可以按需要进行配置<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-max-count=1000<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置 principalSessionName,使得 druid 能够知道当前的 session 的用户是谁<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 根据需要,这个参数的值是 user 信息保存在 session 中的 sessionName<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-session-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 下面这个配置的作用和上面配置的作用类似,这个是通过 Cookie 来识别用户<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-cookie-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 profile 后就能够监控单个 URL 地址调用列表<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.profile-enable=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
我们配置前面五个就可以了,后面三个可以不用配置,各项配置的含义松哥已经在代码中列出来了。
接下来开启 StatViewServlet 的配置,如下:
# 启用内置的监控页面<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 内置监控页面的地址<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 Reset All 功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.reset-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录用户名<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-username=javaboy<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录密码<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 白名单(如果allow没有配置或者为空,则允许所有访问)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.allow=127.0.0.1<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 黑名单(deny 优先于 allow,如果在 deny 列表中,就算在 allow 列表中,也会被拒绝)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.deny=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
配置一下页面地址,配置一下黑白名单即可。
需要注意的是,reset-enable 属性即使设置为 false,重置按钮也会显示,只是点击该按钮并不会重置而已。
好啦,这就完事了。
3. 测试
好啦,接下来我们启动 Spring Boot 项目进行测试。
Spring Boot 项目启动成功后,首先访问如下地址:
此时我们会看到登录认证页面,如下:
输入我们前面配置的用户名/密码(javaboy/123)进行登录,登录成功后,可以看到如下页面:
从标题栏就可以看到,数据源、SQL 监控、SQL 防火墙等功能都是一应俱全。
接下来我们访问:8080/user?username=aaa地址,执行一条 SQL,执行完成后,我们来查看 SQL 监控:
可以看到,此时就有 SQL 执行的监控记录了。
其他的监控数据也都可以看到,我就不一一列举了。如果小伙伴们觉得这里展示的数据不直观,想自己画 HTML 页面,那也是可以的,点击最后面的 JSON API,可以看到每一个监控项的 JSON 地址,拿着 JSON 自己想怎么显示就怎么显示。
4. 去广告
如果想直接用这个监控页面,这个上面有阿里的广告,如下图,公司用的话就特别别扭:
我们可能想去掉这个广告,这也很容易。
首先,经过分析,我们发现广告是由一个叫做 common.js 的文件构建出来的,该文件位于druid-1.2.8.jar!/support/http/resources/js/common.js这里,common.js 文件中有如下几行:
init : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> this.buildFooter();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid.lang.init();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />buildFooter : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> var html ='';<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> $(document.body).append(html);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
大概逻辑就是上面这样,buildFooter 方法负责构建页面末尾的广告,在 init 方法中完成对 buildFooter 方法的调用。
那么想要去除广告,就别调用 buildFooter 方法就行了。
所以我们的去广告思路也很简单,写一个过滤器,拦截下对 common.js 的请求,然后做一点点修改,如下:
@WebFilter(urlPatterns = "/druid/js/common.js")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class RemoveAdFilter implements Filter {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Override<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> String text = Utils.readFromResource("support/http/resources/js/common.js");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> text = text.replace("this.buildFooter();", "");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> servletResponse.getWriter().write(text);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
可以看到,这个过滤器就是拦截/druid/js/common.js请求,拦截到之后,自己去文件中读取 common.js 文件,然后手动替换掉this.buildFooter();这一句就行了,最后再把文件写出去就行了。
当然,记得在启动类中扫描 Filter,如下:
@SpringBootApplication<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@ServletComponentScan("org.javaboy.druid_monitor.filter")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class DruidMonitorApplication {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public static void main(String[] args) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> SpringApplication.run(DruidMonitorApplication.class, args);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
好啦,这就可以啦,有了这个过滤器,广告就没了。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
查看全部
Spring Boot 如何监控 SQL 运行情况?
关注后回复“进群”,拉你进程序员交流群
今天想和大家聊一聊 Druid 中的监控功能。
Druid 数据库连接池相信很多小伙伴都用过,个人感觉 Druid 是阿里比较成功的开源项目了,不像 Fastjson 那么多槽点,Druid 各方面一直都比较出色,功能齐全,使用也方便,基本的用法就不说了,今天我们来看看 Druid 中的监控功能。
1. 准备工作
首先我们来创建一个 Spring Boot 工程,引入 MyBatis 等,如下:
选一下 MyBatis 和 MySQL 驱动,做一个简单的测试案例。
先来连接一下数据库:
spring.datasource.username=root<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.url=jdbc:mysql:///test05?serverTimezone=Asia/Shanghai<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
创建一个 User 实体类,做一个简单的查询案例,如下:
public class User {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private Integer id;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String username;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String address;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String password;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String email;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> //省略 getter/setter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Mapper<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public interface UserMapper {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> List getUserByUsername(String username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Service<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserService {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserMapper userMapper;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUserByUsername(String username){<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userMapper.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@RestController<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserController {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserService userService;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @GetMapping("/user")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUser(String username) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userService.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
UserMapper.xml 如下:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> select * from user where username=#{username}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
一个很简单的测试,没啥好说的。
这个环境搭建大家随意,如果你已经有持久化的案例了,那就直接看第二小节引入 Druid。
现在这个工程默认的使用的数据库连接池是 HikariDataSource,这是 Spring Boot 中默认的一个数据库连接池,其实这个也还不错。
2. 引入 Druid
接下来我们引入 Druid:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> com.alibaba<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid-spring-boot-starter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 1.2.8<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
注意,Spring Boot 引入的 Druid 是上面这个,这个将来配置监控的时候方便一些。
接下来我们在 application.properties 中配置 WebStatFilter,WebStatFilter 用于采集 web-jdbc 关联监控的数据:
# 启用 WebStatFilter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置拦截规则<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.url-pattern=/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 排除一些不必要的 url,这些 URL 不会涉及到 SQL 查询<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 session 统计功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 缺省 sessionStatMaxCount 是 1000 个,我们可以按需要进行配置<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-max-count=1000<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置 principalSessionName,使得 druid 能够知道当前的 session 的用户是谁<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 根据需要,这个参数的值是 user 信息保存在 session 中的 sessionName<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-session-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 下面这个配置的作用和上面配置的作用类似,这个是通过 Cookie 来识别用户<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-cookie-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 profile 后就能够监控单个 URL 地址调用列表<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.profile-enable=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
我们配置前面五个就可以了,后面三个可以不用配置,各项配置的含义松哥已经在代码中列出来了。
接下来开启 StatViewServlet 的配置,如下:
# 启用内置的监控页面<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 内置监控页面的地址<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 Reset All 功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.reset-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录用户名<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-username=javaboy<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录密码<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 白名单(如果allow没有配置或者为空,则允许所有访问)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.allow=127.0.0.1<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 黑名单(deny 优先于 allow,如果在 deny 列表中,就算在 allow 列表中,也会被拒绝)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.deny=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
配置一下页面地址,配置一下黑白名单即可。
需要注意的是,reset-enable 属性即使设置为 false,重置按钮也会显示,只是点击该按钮并不会重置而已。
好啦,这就完事了。
3. 测试
好啦,接下来我们启动 Spring Boot 项目进行测试。
Spring Boot 项目启动成功后,首先访问如下地址:
此时我们会看到登录认证页面,如下:
输入我们前面配置的用户名/密码(javaboy/123)进行登录,登录成功后,可以看到如下页面:
从标题栏就可以看到,数据源、SQL 监控、SQL 防火墙等功能都是一应俱全。
接下来我们访问:8080/user?username=aaa地址,执行一条 SQL,执行完成后,我们来查看 SQL 监控:
可以看到,此时就有 SQL 执行的监控记录了。
其他的监控数据也都可以看到,我就不一一列举了。如果小伙伴们觉得这里展示的数据不直观,想自己画 HTML 页面,那也是可以的,点击最后面的 JSON API,可以看到每一个监控项的 JSON 地址,拿着 JSON 自己想怎么显示就怎么显示。
4. 去广告
如果想直接用这个监控页面,这个上面有阿里的广告,如下图,公司用的话就特别别扭:
我们可能想去掉这个广告,这也很容易。
首先,经过分析,我们发现广告是由一个叫做 common.js 的文件构建出来的,该文件位于druid-1.2.8.jar!/support/http/resources/js/common.js这里,common.js 文件中有如下几行:
init : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> this.buildFooter();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid.lang.init();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />buildFooter : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> var html ='';<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> $(document.body).append(html);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
大概逻辑就是上面这样,buildFooter 方法负责构建页面末尾的广告,在 init 方法中完成对 buildFooter 方法的调用。
那么想要去除广告,就别调用 buildFooter 方法就行了。
所以我们的去广告思路也很简单,写一个过滤器,拦截下对 common.js 的请求,然后做一点点修改,如下:
@WebFilter(urlPatterns = "/druid/js/common.js")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class RemoveAdFilter implements Filter {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Override<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> String text = Utils.readFromResource("support/http/resources/js/common.js");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> text = text.replace("this.buildFooter();", "");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> servletResponse.getWriter().write(text);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
可以看到,这个过滤器就是拦截/druid/js/common.js请求,拦截到之后,自己去文件中读取 common.js 文件,然后手动替换掉this.buildFooter();这一句就行了,最后再把文件写出去就行了。
当然,记得在启动类中扫描 Filter,如下:
@SpringBootApplication<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@ServletComponentScan("org.javaboy.druid_monitor.filter")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class DruidMonitorApplication {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public static void main(String[] args) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> SpringApplication.run(DruidMonitorApplication.class, args);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
好啦,这就可以啦,有了这个过滤器,广告就没了。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
一文详解JVM垃圾收集机制,动图帮你轻松理解大厂面试难点
采集交流 • 优采云 发表了文章 • 0 个评论 • 89 次浏览 • 2022-05-09 12:05
前言
上篇文章已经给大家介绍了 JVM 的架构和运行时数据区 (内存区域),本篇文章将给大家介绍 JVM 的重点内容——垃圾收集。众所周知,相比 C / C++ 等语言,Java 可以省去手动管理内存的繁琐操作,很大程度上解放了 Java 程序员的生产力,而这正是得益于 JVM 的垃圾收集机制和内存分配策略。我们平时写程序时并感知不到这一点,但是如果是在生产环境中,JVM 的不同配置对于服务器性能的影响是非常大的,所以掌握 JVM 调优是高级 Java 工程师的必备技能。正所谓“基础不牢,地动山摇”,在这之前我们先来了解一下底层的 JVM 垃圾收集机制。
既然要介绍垃圾收集机制,就要搞清楚以下几个问题:
哪些内存区域需要进行垃圾收集?
如何判断对象是否可回收?
新的对象是如何进行内存分配的?
如何进行垃圾收集?
本文将按以下行文结构展开,对上述问题一一解答。
需要进行垃圾收集的内存区域;
判断对象是否可回收的方法;
主流的垃圾收集算法介绍;
JVM 的内存分配与垃圾收集机制。
下面开始正文,还是图文并茂的老配方,走起。
一、需要进行垃圾收集的内存区域
先来回顾一下 JVM 的运行时数据区:
JVM 运行时数据区
其中程序计数器、Java 虚拟机栈和本地方法栈都是线程私有的,与其对应的线程是共生关系,随线程而生,随线程而灭,栈中的栈帧也随着方法的进入和退出井然有序地进行入栈和出栈操作。所以这几个区域的内存分配和回收都是有很大确定性的,在方法结束或线程结束时,内存也会随之释放,因此也就不需要考虑这几个区域的内存回收问题了。
而堆和方法区就不一样了,Java 的对象几乎都是在堆上创建出来的,方法区则存储了被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,方法区中的运行时常量池则存放了各种字面量与符号引用,上述的这些数据大部分都是在运行时才能确定的,所以需要进行动态的内存管理。
还要说明一点,JVM 中的垃圾收集器的最主要的关注对象是 Java 堆,因为这里进行垃圾收集的“性价比”是最高的,尤其是在新生代 (后文对分代算法进行介绍) 中的垃圾收集,一次就可以回收 70% - 99% 的内存。而方法区由于垃圾收集判定条件,尤其是类型卸载的判定条件相当苛刻,其回收性价比是非常低的,因此有些垃圾收集器就干脆不支持或不完全支持方法区的垃圾收集,比如 JDK 11 中的 ZGC 收集器就不支持类型卸载。
二、判断对象是否可回收的方法2.1 引用计数法
引用计数法的实现很简单,在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。大部分情况下这个方法是可以发挥作用的,但是在存在循环引用的情况下,引用计数法就无能为力了。比如下面这种情况:
public class Student {<br /> // friend 字段<br /> public Student friend = null;<br /> <br /> public static void test() {<br /> Student a = new Student();<br /> Student b = new Student();<br /> a.friend = b;<br /> b.friend = a;<br /> a = null;<br /> b = null;<br /> System.gc();<br /> }<br />}
上述代码创建了 a 和 b 两个 Student 实例,并把它们各自的 friend 字段赋值为对方,除此之外,这两个对象再无任何引用,然后将它们都赋值为 null,在这种情况下,这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也就无法回收它们。如下图所示:
循环引用
但是在 Java 程序中,a 和 b 是可以被回收的,因为 JVM 并没有使用引用计数法判定对象是否可回收,而是采用了可达性分析法。
2.2 可达性分析法
这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集 (GC Root Set),从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链” (Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,则说明此对象不再被使用,也就可以被回收了。要进行可达性分析就需要先枚举根节点 (GC Roots),在枚举根节点过程中,为防止对象的引用关系发生变化,需要暂停所有用户线程 (垃圾收集之外的线程),这种暂停全部用户线程的行为被称为 (Stop The World)。可达性分析法如下图所示:
可达性分析法
图中绿色的都是位于 GC Root Set 中的 GC Roots,所有与其有关联的对象都是可达的,被标记为蓝色,而所有与其没有任何关联的对象都是不可达的,被标记为灰色。即使是不可达对象,也并非一定会被回收,如果该对象同时满足以下几个条件,那么它仍有“逃生”的可能:
该对象有重写的finalize()方法 (Object 类中的方法);
finalize()方法中将其自身链接到了引用链上;
JVM 此前没有调用过该对象的finalize()方法 (因为 JVM 在收集可回收对象时会调用且仅调用一次该对象的finalize()方法)。
不过由于finalize()方法的运行代价高昂,不确定性大,且无法保证各个对象的调用顺序,所以并不推荐使用。那么 GC Roots 又是何方神圣呢?在 Java 语言中,固定可作为GC Roots的对象包括以下几种:
在虚拟机栈 (栈帧中的本地变量表) 中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。
在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用。
在本地方法栈中JNI (即通常所说的Native方法) 引用的对象。
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常见的异常对象 (比如
NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器。
所有被同步锁 (synchronized关键字) 持有的对象。
反映Java虚拟机内部情况的 JM XBean、JVM TI 中注册的回调、本地代码缓存等。
三、垃圾收集算法介绍3.1 标记-清除算法
标记-清除算法的思想很简单,顾名思义,该算法的过程分为标记和清除两个阶段:首先标记出所有需要回收的对象,其中标记过程就是使用可达性分析法判断对象是否属于垃圾的过程。在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。示意图如下:
标记清除算法
这个算法虽然很简单,但是有两个明显的缺点:
执行效率不稳定。如果 Java 堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
导致内存空间碎片化。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作,非常影响程序运行效率。
3.2 标记-复制算法
标记-复制算法常简称复制算法,这一算法正好解决了标记-清除算法在面对大量可回收对象时执行效率低下的问题。其实现方法也很易懂:在可用内存中划分出两块大小相同的区域,每次只使用其中一块,另一块保持空闲状态,第一块用完的时候,就把存活的对象全部复制到第二块区域,然后把第一块全部清空。如下图所示:
标记-复制算法
这个算法很适合用于对象存活率低的情况,因为它只关注存活对象而无需理会可回收对象,所以 JVM 中新生代的垃圾收集正是采用的这一算法。但是其缺点也很明显,每次都要浪费一半的内存,未免太过奢侈,不过 JVM 中的新生代有更精细的内存划分,比较好地解决了这个问题,见下文。
3.3 标记-整理算法
这个算法完美解决了标记-清除算法的空间碎片化问题,其标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记整理算法
这个算法虽然可以很好地解决空间碎片化问题,但是每次垃圾回收都要移动存活的对象,还要对引用这些对象的地方进行更新,对象移动的操作也需要全程暂停用户线程 (Stop The World)。
3.4 分代收集算法
与其说是算法,不如说是理论。如今大多数虚拟机的实现版本都遵循了“分代收集”的理论进行设计,这个理论可以看作是经验之谈,因为开发人员在开发过程中发现了 JVM 中存活对象的数量和它们的年龄之间有着某种规律,如下图:
JVM 中存活对象数量与年龄之间的关系
在此基础上,人们提出了以下假说:
绝大多数对象都是朝生夕灭的。
熬过越多次垃圾收集过程的对象就越难以消亡。
根据这两个假说,可以把 JVM 的堆内存大致分为新生代和老年代,新生代对象大多存活时间短,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间,所以这一区域一般采用标记-复制算法进行垃圾收集,频率比较高。而老年代则是一些难以消亡的对象,可以采用标记-清除和标记整理算法进行垃圾收集,频率可以低一些。
按照 Hotspot 虚拟机的实现,针对新生代和老年代的垃圾收集又分为不同的类型,也有不同的名词,如下:
部分收集 (Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:新生代收集 (Minor GC / Young GC):指目标只是新生代的垃圾收集。老年代收集 (Major GC / Old GC):指目标只是老年代的垃圾收集,目前只有CMS收集器的并发收集阶段是单独收集老年代的行为。混合收集 (Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
整堆收集 (Full GC):收集整个Java堆和方法区的垃圾收集。
人们经常会混淆 Major GC 和 Full GC,不过这也有情可原,因为这两种 GC 行为都包含了老年代的垃圾收集,而单独的老年代收集 (Major GC) 又比较少见,大多数情况下只要包含老年代收集,就会是整堆收集 (Full GC),不过还是分得清楚一点比较好哈。
四、JVM 的内存分配和垃圾收集机制
经过前面的铺垫,现在终于可以一窥 JVM 的内存分配和垃圾收集机制的真面目了。
4.1 JVM 堆内存的划分
JVM 堆内存划分,从Java 8开始不再有永久代
Java 堆是 JVM 所管理的内存中最大的一块,也是垃圾收集器的管理区域。大多数垃圾收集器都会将堆内存划分为上图所示的几个区域,整体分为新生代和老年代,比例为 1 : 2,新生代又进一步分为 Eden、From Survivor 和 To Survivor,默认比例为 8 : 1 : 1,请注意,可通过 SurvivorRatio 参数进行设置。请注意,从 JDK 8 开始,JVM 中已经不再有永久代的概念了。Java 堆上的无论哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
4.2 分代收集原理4.2.1 新生代中对象的分配与回收
大多数情况下,对象优先在新生代 Eden 区中分配,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。Eden、From Survivor 和 To Survivor 的比例为 8 : 1 : 1,之所以按这个比例是因为绝大多数对象都是朝生夕灭的,垃圾收集时 Eden 存活的对象数量不会太多,Survivor 空间小一点也足以容纳,每次新生代中可用内存空间为整个新生代容量的90% (Eden 的 80% 加上 To Survivor 的 10%),只有From Survivor 空间,即 10% 的新生代是会被“浪费”的。不会像原始的标记-复制算法那样浪费一半的内存空间。From Survivor 和 To Survivor 的空间并不是固定的,而是在 S0 和 S1 之间动态转换的,第一次 Minor GC 时会选择 S1 作为 To Survivor,并将 Eden 中存活的对象复制到其中,并将对象的年龄加1,注意新生代使用的垃圾收集算法是标记-复制算法的改良版。下面是示意图,请注意其中第一步的变色是为了醒目,虚拟机只做了标记存活对象的操作。
第一次 Minor GC 示意图
在后续的 Minor GC 中,S0 和 S1会交替转化为 From Survivor 和 To Survivor,Eden 和 From Survivor 中的存活对象会复制到 To Survivor 中,并将年龄加大 1。如下图所示:
后续 Minor GC 示意图
4.2.2 对象晋升老年代
在以下这些情况下,对象会晋升到老年代。
长期存活对象将进入老年代
对象在 Survivor 区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 设置,这个参数的最大值是15,因为对象年龄信息储存在对象头中,占4个比特 (bit)的内存,所能表示最大数字就是15。
长期存活对象晋升老年代示意图
2.大对象可以直接进入老年代
对于大对象,尤其是很长的字符串,或者元素数量很多的数组,如果分配在 Eden 中,会很容易过早占满 Eden 空间导致 Minor GC,而且大对象在 Eden 和两个 Survivor 之间的来回复制也还会有很大的内存复制开销。所以我们可以通过设置 -XX:PretenureSizeThreshold 的虚拟机参数让大对象直接进入老年代。
3.动态对象年龄判断
为了能更好地适应不同程序的内存状况,HotSpot 虚拟机并不是永远要求对象的年龄必须达到 -XX:MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 -XX:MaxTenuringThreshold 中要求的年龄。
4.空间分配担保 (Handle Promotion)
当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域 (实际上大多数情况下就是老年代) 进行分配担保。在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 - XX:HandlePromotionFailure 参数的设置值是否允许担保失败 (Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者-XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次 Full GC。 查看全部
一文详解JVM垃圾收集机制,动图帮你轻松理解大厂面试难点
前言
上篇文章已经给大家介绍了 JVM 的架构和运行时数据区 (内存区域),本篇文章将给大家介绍 JVM 的重点内容——垃圾收集。众所周知,相比 C / C++ 等语言,Java 可以省去手动管理内存的繁琐操作,很大程度上解放了 Java 程序员的生产力,而这正是得益于 JVM 的垃圾收集机制和内存分配策略。我们平时写程序时并感知不到这一点,但是如果是在生产环境中,JVM 的不同配置对于服务器性能的影响是非常大的,所以掌握 JVM 调优是高级 Java 工程师的必备技能。正所谓“基础不牢,地动山摇”,在这之前我们先来了解一下底层的 JVM 垃圾收集机制。
既然要介绍垃圾收集机制,就要搞清楚以下几个问题:
哪些内存区域需要进行垃圾收集?
如何判断对象是否可回收?
新的对象是如何进行内存分配的?
如何进行垃圾收集?
本文将按以下行文结构展开,对上述问题一一解答。
需要进行垃圾收集的内存区域;
判断对象是否可回收的方法;
主流的垃圾收集算法介绍;
JVM 的内存分配与垃圾收集机制。
下面开始正文,还是图文并茂的老配方,走起。
一、需要进行垃圾收集的内存区域
先来回顾一下 JVM 的运行时数据区:
JVM 运行时数据区
其中程序计数器、Java 虚拟机栈和本地方法栈都是线程私有的,与其对应的线程是共生关系,随线程而生,随线程而灭,栈中的栈帧也随着方法的进入和退出井然有序地进行入栈和出栈操作。所以这几个区域的内存分配和回收都是有很大确定性的,在方法结束或线程结束时,内存也会随之释放,因此也就不需要考虑这几个区域的内存回收问题了。
而堆和方法区就不一样了,Java 的对象几乎都是在堆上创建出来的,方法区则存储了被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,方法区中的运行时常量池则存放了各种字面量与符号引用,上述的这些数据大部分都是在运行时才能确定的,所以需要进行动态的内存管理。
还要说明一点,JVM 中的垃圾收集器的最主要的关注对象是 Java 堆,因为这里进行垃圾收集的“性价比”是最高的,尤其是在新生代 (后文对分代算法进行介绍) 中的垃圾收集,一次就可以回收 70% - 99% 的内存。而方法区由于垃圾收集判定条件,尤其是类型卸载的判定条件相当苛刻,其回收性价比是非常低的,因此有些垃圾收集器就干脆不支持或不完全支持方法区的垃圾收集,比如 JDK 11 中的 ZGC 收集器就不支持类型卸载。
二、判断对象是否可回收的方法2.1 引用计数法
引用计数法的实现很简单,在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。大部分情况下这个方法是可以发挥作用的,但是在存在循环引用的情况下,引用计数法就无能为力了。比如下面这种情况:
public class Student {<br /> // friend 字段<br /> public Student friend = null;<br /> <br /> public static void test() {<br /> Student a = new Student();<br /> Student b = new Student();<br /> a.friend = b;<br /> b.friend = a;<br /> a = null;<br /> b = null;<br /> System.gc();<br /> }<br />}
上述代码创建了 a 和 b 两个 Student 实例,并把它们各自的 friend 字段赋值为对方,除此之外,这两个对象再无任何引用,然后将它们都赋值为 null,在这种情况下,这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也就无法回收它们。如下图所示:
循环引用
但是在 Java 程序中,a 和 b 是可以被回收的,因为 JVM 并没有使用引用计数法判定对象是否可回收,而是采用了可达性分析法。
2.2 可达性分析法
这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集 (GC Root Set),从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链” (Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,则说明此对象不再被使用,也就可以被回收了。要进行可达性分析就需要先枚举根节点 (GC Roots),在枚举根节点过程中,为防止对象的引用关系发生变化,需要暂停所有用户线程 (垃圾收集之外的线程),这种暂停全部用户线程的行为被称为 (Stop The World)。可达性分析法如下图所示:
可达性分析法
图中绿色的都是位于 GC Root Set 中的 GC Roots,所有与其有关联的对象都是可达的,被标记为蓝色,而所有与其没有任何关联的对象都是不可达的,被标记为灰色。即使是不可达对象,也并非一定会被回收,如果该对象同时满足以下几个条件,那么它仍有“逃生”的可能:
该对象有重写的finalize()方法 (Object 类中的方法);
finalize()方法中将其自身链接到了引用链上;
JVM 此前没有调用过该对象的finalize()方法 (因为 JVM 在收集可回收对象时会调用且仅调用一次该对象的finalize()方法)。
不过由于finalize()方法的运行代价高昂,不确定性大,且无法保证各个对象的调用顺序,所以并不推荐使用。那么 GC Roots 又是何方神圣呢?在 Java 语言中,固定可作为GC Roots的对象包括以下几种:
在虚拟机栈 (栈帧中的本地变量表) 中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。
在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用。
在本地方法栈中JNI (即通常所说的Native方法) 引用的对象。
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常见的异常对象 (比如
NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器。
所有被同步锁 (synchronized关键字) 持有的对象。
反映Java虚拟机内部情况的 JM XBean、JVM TI 中注册的回调、本地代码缓存等。
三、垃圾收集算法介绍3.1 标记-清除算法
标记-清除算法的思想很简单,顾名思义,该算法的过程分为标记和清除两个阶段:首先标记出所有需要回收的对象,其中标记过程就是使用可达性分析法判断对象是否属于垃圾的过程。在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。示意图如下:
标记清除算法
这个算法虽然很简单,但是有两个明显的缺点:
执行效率不稳定。如果 Java 堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
导致内存空间碎片化。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作,非常影响程序运行效率。
3.2 标记-复制算法
标记-复制算法常简称复制算法,这一算法正好解决了标记-清除算法在面对大量可回收对象时执行效率低下的问题。其实现方法也很易懂:在可用内存中划分出两块大小相同的区域,每次只使用其中一块,另一块保持空闲状态,第一块用完的时候,就把存活的对象全部复制到第二块区域,然后把第一块全部清空。如下图所示:
标记-复制算法
这个算法很适合用于对象存活率低的情况,因为它只关注存活对象而无需理会可回收对象,所以 JVM 中新生代的垃圾收集正是采用的这一算法。但是其缺点也很明显,每次都要浪费一半的内存,未免太过奢侈,不过 JVM 中的新生代有更精细的内存划分,比较好地解决了这个问题,见下文。
3.3 标记-整理算法
这个算法完美解决了标记-清除算法的空间碎片化问题,其标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记整理算法
这个算法虽然可以很好地解决空间碎片化问题,但是每次垃圾回收都要移动存活的对象,还要对引用这些对象的地方进行更新,对象移动的操作也需要全程暂停用户线程 (Stop The World)。
3.4 分代收集算法
与其说是算法,不如说是理论。如今大多数虚拟机的实现版本都遵循了“分代收集”的理论进行设计,这个理论可以看作是经验之谈,因为开发人员在开发过程中发现了 JVM 中存活对象的数量和它们的年龄之间有着某种规律,如下图:
JVM 中存活对象数量与年龄之间的关系
在此基础上,人们提出了以下假说:
绝大多数对象都是朝生夕灭的。
熬过越多次垃圾收集过程的对象就越难以消亡。
根据这两个假说,可以把 JVM 的堆内存大致分为新生代和老年代,新生代对象大多存活时间短,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间,所以这一区域一般采用标记-复制算法进行垃圾收集,频率比较高。而老年代则是一些难以消亡的对象,可以采用标记-清除和标记整理算法进行垃圾收集,频率可以低一些。
按照 Hotspot 虚拟机的实现,针对新生代和老年代的垃圾收集又分为不同的类型,也有不同的名词,如下:
部分收集 (Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:新生代收集 (Minor GC / Young GC):指目标只是新生代的垃圾收集。老年代收集 (Major GC / Old GC):指目标只是老年代的垃圾收集,目前只有CMS收集器的并发收集阶段是单独收集老年代的行为。混合收集 (Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
整堆收集 (Full GC):收集整个Java堆和方法区的垃圾收集。
人们经常会混淆 Major GC 和 Full GC,不过这也有情可原,因为这两种 GC 行为都包含了老年代的垃圾收集,而单独的老年代收集 (Major GC) 又比较少见,大多数情况下只要包含老年代收集,就会是整堆收集 (Full GC),不过还是分得清楚一点比较好哈。
四、JVM 的内存分配和垃圾收集机制
经过前面的铺垫,现在终于可以一窥 JVM 的内存分配和垃圾收集机制的真面目了。
4.1 JVM 堆内存的划分
JVM 堆内存划分,从Java 8开始不再有永久代
Java 堆是 JVM 所管理的内存中最大的一块,也是垃圾收集器的管理区域。大多数垃圾收集器都会将堆内存划分为上图所示的几个区域,整体分为新生代和老年代,比例为 1 : 2,新生代又进一步分为 Eden、From Survivor 和 To Survivor,默认比例为 8 : 1 : 1,请注意,可通过 SurvivorRatio 参数进行设置。请注意,从 JDK 8 开始,JVM 中已经不再有永久代的概念了。Java 堆上的无论哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
4.2 分代收集原理4.2.1 新生代中对象的分配与回收
大多数情况下,对象优先在新生代 Eden 区中分配,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。Eden、From Survivor 和 To Survivor 的比例为 8 : 1 : 1,之所以按这个比例是因为绝大多数对象都是朝生夕灭的,垃圾收集时 Eden 存活的对象数量不会太多,Survivor 空间小一点也足以容纳,每次新生代中可用内存空间为整个新生代容量的90% (Eden 的 80% 加上 To Survivor 的 10%),只有From Survivor 空间,即 10% 的新生代是会被“浪费”的。不会像原始的标记-复制算法那样浪费一半的内存空间。From Survivor 和 To Survivor 的空间并不是固定的,而是在 S0 和 S1 之间动态转换的,第一次 Minor GC 时会选择 S1 作为 To Survivor,并将 Eden 中存活的对象复制到其中,并将对象的年龄加1,注意新生代使用的垃圾收集算法是标记-复制算法的改良版。下面是示意图,请注意其中第一步的变色是为了醒目,虚拟机只做了标记存活对象的操作。
第一次 Minor GC 示意图
在后续的 Minor GC 中,S0 和 S1会交替转化为 From Survivor 和 To Survivor,Eden 和 From Survivor 中的存活对象会复制到 To Survivor 中,并将年龄加大 1。如下图所示:
后续 Minor GC 示意图
4.2.2 对象晋升老年代
在以下这些情况下,对象会晋升到老年代。
长期存活对象将进入老年代
对象在 Survivor 区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 设置,这个参数的最大值是15,因为对象年龄信息储存在对象头中,占4个比特 (bit)的内存,所能表示最大数字就是15。
长期存活对象晋升老年代示意图
2.大对象可以直接进入老年代
对于大对象,尤其是很长的字符串,或者元素数量很多的数组,如果分配在 Eden 中,会很容易过早占满 Eden 空间导致 Minor GC,而且大对象在 Eden 和两个 Survivor 之间的来回复制也还会有很大的内存复制开销。所以我们可以通过设置 -XX:PretenureSizeThreshold 的虚拟机参数让大对象直接进入老年代。
3.动态对象年龄判断
为了能更好地适应不同程序的内存状况,HotSpot 虚拟机并不是永远要求对象的年龄必须达到 -XX:MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 -XX:MaxTenuringThreshold 中要求的年龄。
4.空间分配担保 (Handle Promotion)
当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域 (实际上大多数情况下就是老年代) 进行分配担保。在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 - XX:HandlePromotionFailure 参数的设置值是否允许担保失败 (Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者-XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次 Full GC。
如何有效的采集微信公众号文章
采集交流 • 优采云 发表了文章 • 0 个评论 • 124 次浏览 • 2022-05-05 05:10
有个人公众号的用户一般也有其他自媒体账号,在花费了很长时间写好了一篇文章以后终于可以放松一下,但是想着还有好几个其他平台需要复制粘贴着实头大,这个工作既没有技术含量也提现不出文笔。我们下面分享几种获取公众号的方式。
第一种:人工-手动复制粘贴
这也是最笨的一种方法,手动切换平台登录,然后打开编辑器复制粘贴,然后手动发布。
优势:如果有错误一目了然,不同的平台有不同的限制,比如标题字数,封面图片等。
劣势:手动操作浪费人力,效率低下。
第二种:手动-用数据采集工具
下载数据采集工具,手动输入链接可以下载文章内容,然后导出成word或者其他格式。
优势:无需技术配合
劣势:需要手动操作,先获取内容,然后导出,然后登录自己平台手动导入。
第三种:技术-抓取(走搜狗)
可以在程序中做定时获取固定公众号同步文章操作,通过搜狗浏览器搜索微信号,得到文章列表,然后文章列表中有文章的url,得到url后通过程序下载文章内容然后可以继续后续操作。
优势:减少人工操作,可实现半自动化自动发布
劣势:如果公众号很多,请求太频繁会有验证码出现,程序很难自动识别,一旦验证码还需要手动验证。而且无法实时同步,即公众号发完后不会立马同步。具体延时多少根据自己的请求频率决定。还有一点就是获取的链接是临时链接,必须在有效时间范围内把文章内容请求下来。
第四种:技术-抓取(走微信后台)
在微信后台文章编辑中有一个插入超链接的功能。通过这个接口可以搜索公众号,然后得到历史文章,得到文章链接后可以通过后台程序下载下来。
优势:减少人工操作,可实现半自动化自动发布。此链接是永久链接,任何时候都可用。
劣势:同第二种如果请求太频繁也会有拦截导致接口无法调用。
第五种:技术-通过一键建站推送
你只需要在一键建站平台中开通数据采集功能,配置好你接收数据的接口,一旦公众号中有新的文章发布将给你推送最新的内容
优势:延时时间短。
操作简单代码量少再也不用担心技术无法实现。
真正实现完全托管,全自动化。
劣势:付费版,免费额度很少。但是价格也好像不贵几分钱一条吧。
以上几种方式都已经亲测过了,如果有更多更好的方式记得联系我,我也好去试试,如果有需要帮助或者技术不明白的可以加我沟通交流。 查看全部
如何有效的采集微信公众号文章
有个人公众号的用户一般也有其他自媒体账号,在花费了很长时间写好了一篇文章以后终于可以放松一下,但是想着还有好几个其他平台需要复制粘贴着实头大,这个工作既没有技术含量也提现不出文笔。我们下面分享几种获取公众号的方式。
第一种:人工-手动复制粘贴
这也是最笨的一种方法,手动切换平台登录,然后打开编辑器复制粘贴,然后手动发布。
优势:如果有错误一目了然,不同的平台有不同的限制,比如标题字数,封面图片等。
劣势:手动操作浪费人力,效率低下。
第二种:手动-用数据采集工具
下载数据采集工具,手动输入链接可以下载文章内容,然后导出成word或者其他格式。
优势:无需技术配合
劣势:需要手动操作,先获取内容,然后导出,然后登录自己平台手动导入。
第三种:技术-抓取(走搜狗)
可以在程序中做定时获取固定公众号同步文章操作,通过搜狗浏览器搜索微信号,得到文章列表,然后文章列表中有文章的url,得到url后通过程序下载文章内容然后可以继续后续操作。
优势:减少人工操作,可实现半自动化自动发布
劣势:如果公众号很多,请求太频繁会有验证码出现,程序很难自动识别,一旦验证码还需要手动验证。而且无法实时同步,即公众号发完后不会立马同步。具体延时多少根据自己的请求频率决定。还有一点就是获取的链接是临时链接,必须在有效时间范围内把文章内容请求下来。
第四种:技术-抓取(走微信后台)
在微信后台文章编辑中有一个插入超链接的功能。通过这个接口可以搜索公众号,然后得到历史文章,得到文章链接后可以通过后台程序下载下来。
优势:减少人工操作,可实现半自动化自动发布。此链接是永久链接,任何时候都可用。
劣势:同第二种如果请求太频繁也会有拦截导致接口无法调用。
第五种:技术-通过一键建站推送
你只需要在一键建站平台中开通数据采集功能,配置好你接收数据的接口,一旦公众号中有新的文章发布将给你推送最新的内容
优势:延时时间短。
操作简单代码量少再也不用担心技术无法实现。
真正实现完全托管,全自动化。
劣势:付费版,免费额度很少。但是价格也好像不贵几分钱一条吧。
以上几种方式都已经亲测过了,如果有更多更好的方式记得联系我,我也好去试试,如果有需要帮助或者技术不明白的可以加我沟通交流。
网络爬虫实例系列 —— 搜狗微信文章采集方案
采集交流 • 优采云 发表了文章 • 0 个评论 • 248 次浏览 • 2022-05-05 04:05
微信和搜狗合作推出微信搜索后,让各家媒体检测平台如获珍宝,终于可以获取到微信的文章数据了;但好景不长,由于大家基本只有这一个接口来获取微信的数据,导致搜狗的压力倍增,先后出现减少返回搜索结果条数,反爬虫输入验证码的情况,导致大家又都很难抓取微信数据;不过后来搜狗又针对登录用户推出了关键字订阅功能,结合这些情况,下面梳理了采集搜狗微信文章的两个方案,仅供参考。
方案一:利用搜狗微信文章搜索接口
调用如下的URL便可访问搜狗微信关键字搜索文章的结果,其中参数query为搜索关键字,值为UTF-8编码的字符串。该URL返回发结果为10条文章的搜索结果,可以直接解析到搜索出的文章名、文章URL、公众号及摘要等信息。修改page参数的值,便可以进行翻页,得到更多的搜索结果。
%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB&sut=13695&lkt=0%2C0%2C0&type=2&sst0=68&page=1&ie=utf8&w=01019900&dr=1
优点:
实现简单,搜索的结果和关键字匹配程度比较高
缺点:
最多只能翻10页,而且网站反爬虫导致屏蔽比较严重
方案二:利用搜狗微信提供的关键字订阅接口
在搜狗微信中,对于登录的用户,搜索的关键字可以添加到订阅列表中,下次可以直接查看自己订阅关键字相关的文章。该功能限制每个登录用户最多可以添加20个订阅关键字。
A. 下面这个URL为获取登录用户已经订阅关键字的列表,其中包括关键字和对应的wordId,该ID可以用于后续访问该关键字在订阅中相关的文章。其中登录用户的标识是注册的帐号,一参数uid传入。
uid=yongzhong15%&_=28
B. 下面这个URL为指定的帐号添加订阅关键字的接口,通过该接口,可以填加帐号订阅的关键字。
uid=yongzhong15%&word=%E5%9B%BE%E8%AE%BA&_=71
C. 下面这个URL为指定的帐号删除指定的关键字,其中关键字是指订阅关键字列表中对应的wordId来指定
uid=yongzhong15%&id=49529&_=72
D. 下面这个URL返回指定用户订阅的其中一个关键字的文章列表,其中文件列表根据start参数进行翻页,关键字是使用wordid来指定。
uid=yongzhong15%&start=0&num=10&wordid=49528&clear=1&_=41
根据以上A/B/C/D四类API接口,便可以查询注册的帐号下的订阅关键字列表,添加、删除帐号下的关键字,并可以获取到各个订阅关键字对应的文章信息。
优点:
订阅API由搜狗官方免费提供,不会被屏蔽
缺点:
每个帐号最多只能订阅20个关键字,使用不方便;而且对于每个关键字,返回的文章个数远少于直接从搜索接口搜索到的相关文章数,即获取到的数据不全。 查看全部
网络爬虫实例系列 —— 搜狗微信文章采集方案
微信和搜狗合作推出微信搜索后,让各家媒体检测平台如获珍宝,终于可以获取到微信的文章数据了;但好景不长,由于大家基本只有这一个接口来获取微信的数据,导致搜狗的压力倍增,先后出现减少返回搜索结果条数,反爬虫输入验证码的情况,导致大家又都很难抓取微信数据;不过后来搜狗又针对登录用户推出了关键字订阅功能,结合这些情况,下面梳理了采集搜狗微信文章的两个方案,仅供参考。
方案一:利用搜狗微信文章搜索接口
调用如下的URL便可访问搜狗微信关键字搜索文章的结果,其中参数query为搜索关键字,值为UTF-8编码的字符串。该URL返回发结果为10条文章的搜索结果,可以直接解析到搜索出的文章名、文章URL、公众号及摘要等信息。修改page参数的值,便可以进行翻页,得到更多的搜索结果。
%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB&sut=13695&lkt=0%2C0%2C0&type=2&sst0=68&page=1&ie=utf8&w=01019900&dr=1
优点:
实现简单,搜索的结果和关键字匹配程度比较高
缺点:
最多只能翻10页,而且网站反爬虫导致屏蔽比较严重
方案二:利用搜狗微信提供的关键字订阅接口
在搜狗微信中,对于登录的用户,搜索的关键字可以添加到订阅列表中,下次可以直接查看自己订阅关键字相关的文章。该功能限制每个登录用户最多可以添加20个订阅关键字。
A. 下面这个URL为获取登录用户已经订阅关键字的列表,其中包括关键字和对应的wordId,该ID可以用于后续访问该关键字在订阅中相关的文章。其中登录用户的标识是注册的帐号,一参数uid传入。
uid=yongzhong15%&_=28
B. 下面这个URL为指定的帐号添加订阅关键字的接口,通过该接口,可以填加帐号订阅的关键字。
uid=yongzhong15%&word=%E5%9B%BE%E8%AE%BA&_=71
C. 下面这个URL为指定的帐号删除指定的关键字,其中关键字是指订阅关键字列表中对应的wordId来指定
uid=yongzhong15%&id=49529&_=72
D. 下面这个URL返回指定用户订阅的其中一个关键字的文章列表,其中文件列表根据start参数进行翻页,关键字是使用wordid来指定。
uid=yongzhong15%&start=0&num=10&wordid=49528&clear=1&_=41
根据以上A/B/C/D四类API接口,便可以查询注册的帐号下的订阅关键字列表,添加、删除帐号下的关键字,并可以获取到各个订阅关键字对应的文章信息。
优点:
订阅API由搜狗官方免费提供,不会被屏蔽
缺点:
每个帐号最多只能订阅20个关键字,使用不方便;而且对于每个关键字,返回的文章个数远少于直接从搜索接口搜索到的相关文章数,即获取到的数据不全。
连载|浅谈红队中的外网信息收集(一)
采集交流 • 优采云 发表了文章 • 0 个评论 • 69 次浏览 • 2022-05-01 14:10
前言
最近在对以往所学习的有关红队的知识点进行梳理总结,这里主要参考了 ATT&CK 矩阵模型,不过对其进行了简化,同时加入了一些国内特有的情况放了进去。
大体上会按照外网信息收集、打点、权限维持、提权、内网信息收集、横向移动、痕迹清理这样的顺序展开。
因为是梳理总结性的文章,所以文章的侧重点在于「面」而不在于具体的某个「点」,因此文中的具体技术细节不会展开去谈,不然内容会很多。
想了解具体细节的读者可以去看看我的个人公众号 TeamsSix 里的历史文章,里面会有针对一些点展开描述的文章。
受限于个人水平,文中难免会出现错误或者描述不当的地方,还望在评论处指出,望谅解。
确定目标
当开始做信息收集之前,肯定是要先确定目标的,在红队项目或者 HW 项目中,一般目标都是一个公司的名称,然后通过这个公司的名称获取各种信息,接着开展外网打点、内网渗透等等工作。
在我们得知目标公司名称后,就可以开展信息收集的工作了。
外网信息收集
我这里梳理了大概以下这些信息收集的方式,当然肯定是不全的,欢迎大家在评论区一起补充讨论:
1、组织股权结构
拿到公司名称后,先不用急着查备案、找域名,而是先看看这家公司的股权构成,因为一般一家公司的子公司也是可以作为目标去打的,不过有时是要求 50% 持股或者 100% 持股,这个就要看具体实际情况了。
比较常见的查询公司组织股权结构的网站有天眼查、企查查、爱企查、小蓝本、钉钉企典等等。
如果目标持股公司不多,可以直接看股权穿透图,比较直观;如果持股公司比较多,股权穿透图看着就比较费力了。
除了股权穿透之外,还可以看它的对外投资信息
这两个地方都可以查到有目标持股的公司。
如果目标比较少一个一个子公司的去看还好,但如果目标很多,那这效率就很低了,好在现在也有了现成的工具。
ENScanGo
ENScanGo 是现有开源项目 ENScan 的升级版本,工具地址:
这是一款由狼组安全团队的 Keac 师傅写的专门用来解决企业信息收集难的问题的工具,可以一键收集目标及其控股公司的 ICP 备案、APP、小程序、微信公众号等信息然后聚合导出。
例如我这里搜集「北京百度网讯科技有限公司」以及他持股了 50% 的公司信息。
enscan -n 北京百度网讯科技有限公司 -invest-num 50
收集后的结果如下:
这样一来,直接省去了收集 ICP 备案的步骤,一键获得了目标公司及其子公司的公司名称、app、微信公众号、ICP 备案等信息。
2、主域名查询
主域名查询可以分为备案域名查询和未备案域名查询。
备案域名查询
除了上面从企业信息查询网站中获取到备案信息外,最全也是最准确的方法就是去国家的备案信息查询网站里查询了,地址为:。
除了官方的渠道外,还有一些第三方的备案域名查询站点,比如站长之家等等。
未备案域名查询
有些企业会把自己的其他业务站点放在网站尾部,里面也许会包含未备案的站点。
3、子域获取
比较常见的工具就是 OneForAll,除此之外还有 amass、subfinder、xray、ksubdomain 等等。
如果提前知道目标,还可以提前收集一波子域,然后项目快开始的时候,再收集一波子域,将两次收集的结果做下对比,优先打新增子域。
4、端口扫描
一般比较常见的可能就是 nmap 和 masscan 了,这里分享一个 nmap 快速扫描全部端口的命令。
nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 -iL ip.txt -oX output.xml
不过除了这些方法外,fscan 其实也可以拿来做外网的端口扫描,而且速度更快。
比如用 fscan 只扫描端口,不做漏洞扫描
fscan -hf hosts.txt --nopoc -t 100
fscan 默认线程是 600,因为是外网扫描 600 的线程就比较大了,所以这里设置了 100,如果感觉扫描结果不理想,线程还可以设置的再小点。
端口扫描可以结合空间搜索引擎的结果,如果通过空间搜索引擎发现目标存在很多的高位端口,那么在进行端口扫描的时候就要记得也把这些高位端口加上。
5、指纹识别
指纹识别是我个人觉着非常重要的一点,因为指纹识别的结果对打点的帮助是很大的,可以让打点更有针对性,同时也会节省很多时间。
比较常见的在线指纹查询网站有 godeye 和云悉等,工具有 observer_ward 和 Ehole 等。
6、空间搜索引擎
擅用空间搜索引擎,有时可以在最初刚拿确定目标的时候就发现目标弱点。
目前比较常见的空间搜索引擎有 Fofa、Shodan、360 夸克、奇安信全球鹰、知道创宇 ZoomEye 等等。
常见的工具有 Fofa_Viewer、FofaX、Kunyu,其中 Fofa_Viewer 为图形化界面,使用友好。
下载地址:
FofaX 为命令行界面,FofaX 可以结合其他工具联动使用,除了这两个工具调用 Fofa 接口外,上面提到的 Ehole 也可以调用 Fofa 查询。
Kunyu 调用的是 ZoomEye 的接口,工具下载地址:
7、api 接口
获取 api 接口常用的工具有 jsinfo、findsomething、jsfinder、BurpJSLinkFinder 等等。
如果找到了一些未授权接口,也许就可以搞到一些高价值信息,比如大量敏感信息泄露之类的。
另外在翻 js 文件的时候,可以关注下有没有以 runtime 命名的 js 文件,因为在这种 js 文件中会包含其他 js 文件的名称(包括当前页面没有加载的 js 文件),这样利用 runtime js 文件就发现了更多的 js 文件,使得找到 api 接口的概率又大了些。
8、目录获取
目录扫描比较常用的工具有 dirsearch、ffuf
ffuf 更侧重于 FFUZ,不过不管是目录扫描还是 FFUZ ,扫描的结果都在于字典,Github 上 4k 多个 star 的字典:
9、邮箱地址获取
邮箱地址比较常用的方法有直接通过搜索引擎找网上公开的邮箱信息,这种往往会指向目标的网站中,比如目标某个网页的附件中包含有邮箱等信息。
之外还可以使用 Github 搜索目标公司开发者在代码中注释的邮箱信息,其实不太明白为什么开发者都喜欢把自己的邮箱注释到代码里。
也可以通过领英找到目标公司的员工姓名,通过「拼音+@公司域名」的方法去构造员工邮箱。
也有一些网站可以查询邮箱,这种比较方便,比如以下网站:
另外如果收集到了目标的 outlook 站点,也可以尝试去爆破邮箱用户名。
10、网盘信息
网盘信息里有时也会发现不少好东西,这类网站也很多,可以在爱达杂货铺导航站里找到很多网盘搜索类站点。
11、其他信息
其他的信息比如 app、小程序、供应商、外包合作商、公众号等,或多或少都可以从上面的组织股权架构类网站中查询到,或者使用 ENScan 也可以。
其中比较值得注意是的供应商和外包合作商,如果拿下供应商也许可以直接进入目标内网,如果拿下外包合作商则可以借助这一层关系进行社工或者尝试进入目标内网等操作。
后记
红队中的信息收集当然远不止上面说到的,其他比较常用的还有资产监控平台、社工库、自动化信息收集工具(比如 ShuiZe)以及各种内部红队平台等等,这里篇幅有限就不再展开了。 查看全部
连载|浅谈红队中的外网信息收集(一)
前言
最近在对以往所学习的有关红队的知识点进行梳理总结,这里主要参考了 ATT&CK 矩阵模型,不过对其进行了简化,同时加入了一些国内特有的情况放了进去。
大体上会按照外网信息收集、打点、权限维持、提权、内网信息收集、横向移动、痕迹清理这样的顺序展开。
因为是梳理总结性的文章,所以文章的侧重点在于「面」而不在于具体的某个「点」,因此文中的具体技术细节不会展开去谈,不然内容会很多。
想了解具体细节的读者可以去看看我的个人公众号 TeamsSix 里的历史文章,里面会有针对一些点展开描述的文章。
受限于个人水平,文中难免会出现错误或者描述不当的地方,还望在评论处指出,望谅解。
确定目标
当开始做信息收集之前,肯定是要先确定目标的,在红队项目或者 HW 项目中,一般目标都是一个公司的名称,然后通过这个公司的名称获取各种信息,接着开展外网打点、内网渗透等等工作。
在我们得知目标公司名称后,就可以开展信息收集的工作了。
外网信息收集
我这里梳理了大概以下这些信息收集的方式,当然肯定是不全的,欢迎大家在评论区一起补充讨论:
1、组织股权结构
拿到公司名称后,先不用急着查备案、找域名,而是先看看这家公司的股权构成,因为一般一家公司的子公司也是可以作为目标去打的,不过有时是要求 50% 持股或者 100% 持股,这个就要看具体实际情况了。
比较常见的查询公司组织股权结构的网站有天眼查、企查查、爱企查、小蓝本、钉钉企典等等。
如果目标持股公司不多,可以直接看股权穿透图,比较直观;如果持股公司比较多,股权穿透图看着就比较费力了。
除了股权穿透之外,还可以看它的对外投资信息
这两个地方都可以查到有目标持股的公司。
如果目标比较少一个一个子公司的去看还好,但如果目标很多,那这效率就很低了,好在现在也有了现成的工具。
ENScanGo
ENScanGo 是现有开源项目 ENScan 的升级版本,工具地址:
这是一款由狼组安全团队的 Keac 师傅写的专门用来解决企业信息收集难的问题的工具,可以一键收集目标及其控股公司的 ICP 备案、APP、小程序、微信公众号等信息然后聚合导出。
例如我这里搜集「北京百度网讯科技有限公司」以及他持股了 50% 的公司信息。
enscan -n 北京百度网讯科技有限公司 -invest-num 50
收集后的结果如下:
这样一来,直接省去了收集 ICP 备案的步骤,一键获得了目标公司及其子公司的公司名称、app、微信公众号、ICP 备案等信息。
2、主域名查询
主域名查询可以分为备案域名查询和未备案域名查询。
备案域名查询
除了上面从企业信息查询网站中获取到备案信息外,最全也是最准确的方法就是去国家的备案信息查询网站里查询了,地址为:。
除了官方的渠道外,还有一些第三方的备案域名查询站点,比如站长之家等等。
未备案域名查询
有些企业会把自己的其他业务站点放在网站尾部,里面也许会包含未备案的站点。
3、子域获取
比较常见的工具就是 OneForAll,除此之外还有 amass、subfinder、xray、ksubdomain 等等。
如果提前知道目标,还可以提前收集一波子域,然后项目快开始的时候,再收集一波子域,将两次收集的结果做下对比,优先打新增子域。
4、端口扫描
一般比较常见的可能就是 nmap 和 masscan 了,这里分享一个 nmap 快速扫描全部端口的命令。
nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 -iL ip.txt -oX output.xml
不过除了这些方法外,fscan 其实也可以拿来做外网的端口扫描,而且速度更快。
比如用 fscan 只扫描端口,不做漏洞扫描
fscan -hf hosts.txt --nopoc -t 100
fscan 默认线程是 600,因为是外网扫描 600 的线程就比较大了,所以这里设置了 100,如果感觉扫描结果不理想,线程还可以设置的再小点。
端口扫描可以结合空间搜索引擎的结果,如果通过空间搜索引擎发现目标存在很多的高位端口,那么在进行端口扫描的时候就要记得也把这些高位端口加上。
5、指纹识别
指纹识别是我个人觉着非常重要的一点,因为指纹识别的结果对打点的帮助是很大的,可以让打点更有针对性,同时也会节省很多时间。
比较常见的在线指纹查询网站有 godeye 和云悉等,工具有 observer_ward 和 Ehole 等。
6、空间搜索引擎
擅用空间搜索引擎,有时可以在最初刚拿确定目标的时候就发现目标弱点。
目前比较常见的空间搜索引擎有 Fofa、Shodan、360 夸克、奇安信全球鹰、知道创宇 ZoomEye 等等。
常见的工具有 Fofa_Viewer、FofaX、Kunyu,其中 Fofa_Viewer 为图形化界面,使用友好。
下载地址:
FofaX 为命令行界面,FofaX 可以结合其他工具联动使用,除了这两个工具调用 Fofa 接口外,上面提到的 Ehole 也可以调用 Fofa 查询。
Kunyu 调用的是 ZoomEye 的接口,工具下载地址:
7、api 接口
获取 api 接口常用的工具有 jsinfo、findsomething、jsfinder、BurpJSLinkFinder 等等。
如果找到了一些未授权接口,也许就可以搞到一些高价值信息,比如大量敏感信息泄露之类的。
另外在翻 js 文件的时候,可以关注下有没有以 runtime 命名的 js 文件,因为在这种 js 文件中会包含其他 js 文件的名称(包括当前页面没有加载的 js 文件),这样利用 runtime js 文件就发现了更多的 js 文件,使得找到 api 接口的概率又大了些。
8、目录获取
目录扫描比较常用的工具有 dirsearch、ffuf
ffuf 更侧重于 FFUZ,不过不管是目录扫描还是 FFUZ ,扫描的结果都在于字典,Github 上 4k 多个 star 的字典:
9、邮箱地址获取
邮箱地址比较常用的方法有直接通过搜索引擎找网上公开的邮箱信息,这种往往会指向目标的网站中,比如目标某个网页的附件中包含有邮箱等信息。
之外还可以使用 Github 搜索目标公司开发者在代码中注释的邮箱信息,其实不太明白为什么开发者都喜欢把自己的邮箱注释到代码里。
也可以通过领英找到目标公司的员工姓名,通过「拼音+@公司域名」的方法去构造员工邮箱。
也有一些网站可以查询邮箱,这种比较方便,比如以下网站:
另外如果收集到了目标的 outlook 站点,也可以尝试去爆破邮箱用户名。
10、网盘信息
网盘信息里有时也会发现不少好东西,这类网站也很多,可以在爱达杂货铺导航站里找到很多网盘搜索类站点。
11、其他信息
其他的信息比如 app、小程序、供应商、外包合作商、公众号等,或多或少都可以从上面的组织股权架构类网站中查询到,或者使用 ENScan 也可以。
其中比较值得注意是的供应商和外包合作商,如果拿下供应商也许可以直接进入目标内网,如果拿下外包合作商则可以借助这一层关系进行社工或者尝试进入目标内网等操作。
后记
红队中的信息收集当然远不止上面说到的,其他比较常用的还有资产监控平台、社工库、自动化信息收集工具(比如 ShuiZe)以及各种内部红队平台等等,这里篇幅有限就不再展开了。
【爆款文章】集成服务与RPA的强强联袂,速来围观!
采集交流 • 优采云 发表了文章 • 0 个评论 • 75 次浏览 • 2022-08-17 14:29
金蝶云·苍穹V5.0新特性系列文章持续更新
每工作日早上07:45准时推送
为你带来最新产品资讯
老师,业务上有个需求是把某异构系统的采购订单集成到星瀚系统中,可对方系统不提供接口,没办法获取数据啊。
我的想法是用RPA机器人自动登录该异构系统获取采购订单信息,保存到Excel文件里面,然后再到集成服务云使用离线导入功能手工上传文件,设置好字段映射关系之后加载到星瀚当中。
但手工设置费时费力,有没有办法让这个流程自动化完成而不需要人工参与呢?
开发小白
集成专家
很简单,服务流程最新发布特性支持配置RPA节点。只需稍微改造RPA机器人,让他多做一点事情(稍后揭晓),再把RPA组装到服务流程中即可实现RPA集成。
通过该方案,只需执行一个服务流程就能完成数据的获取、转换和加载步骤了~
听起来很棒,迫不及待想请老师指教了呢。
开发小白
集成专家
那就让我们赶紧开始吧!
适用版本
该功能适用版本为金蝶云·苍穹V4.0.016及以上。
特性展示
RPA是通过模拟用户界面操作的方式来完成自动化的一项技术。通过配置集成服务云服务流程的RPA节点,可以在集成服务中嵌入RPA流程,将集成云从各个系统获取到的数据信息作用于更广泛的业务场景。
接下来,小编将以某异构系统的采购订单集成到星瀚采购订单为例,为大家介绍服务流程如何实现RPA集成。
01 整体思路
通过RPA模拟前台操作,登录异构系统,打开采购订单,获取采购订单数据并上传Excel文件到FTP服务器中。
然后,集成服务云通过服务编排组装该RPA节点后,获取相应数据,其他节点进行数据的转换、映射和加载,完成后将执行结果通过通知节点短信通知到用户。
服务流程集成RPA案例示例
02实现步骤
步骤一:RPA控制台参数配置
使用该功能的前提是【系统服务云】→【配置工具】→【系统参数】中配置了RPA控制台的参数信息。如下图所示:
注:没有此参数请联系RPA部门提供。
RPA控制台系统集成参数
步骤二:用户授权与查看
配置服务流程的用户需要授权RPA通用角色。如下图所示:
RPA通用角色
完成角色授权后,登录RPA控制台即可查看当前用户可使用的RPA流程。如下图所示:
查看RPA流程
步骤三:设计RPA流程
RPA流程中,通过登录异构系统、打开采购订单、获取采购订单数据和保存数据并上传Excel文件四个子流程完成异构系统的采购订单信息的数据获取。
(注:此案例根据需求场景改造RPA流程,增加上传数据文件到FTP服务器子流程,非必选改造。)
RPA主流程 - 登录/打开采购订单/获取数据/上传
各子流程配置详情如下图所示:
左右滑动查看更多>>(从左至右依次为登录异构系统、打开采购订单、获取采购订单数据、保存并上传Excel文件)
步骤四:配置服务流程
创建服务流程后,在流程图中配置RPA节点,选择RPA流程和执行流程的RPA机器人。如下图所示:
服务流程中嵌入RPA节点
RPA节点详情
步骤五:运行监控
双击流程实例中的RPA节点,可查看RPA任务执行详情,如下图所示:
服务流程实例与RPA节点
RPA节点执行详情
亮点价值
亮点一:集成服务流程和RPA强强联合
打通集成服务云与RPA控制台,轻松实现异构系统的集成与标准化流程的执行、多渠道采集、加工与处理数据,提高工作效率,助力企业业务集成自动化。
亮点二:流程配置简单,执行过程可追溯
服务流程中可以直接配置一个或多个RPA节点,通过用户获取RPA流程与RPA机器人信息,在服务流程执行时自动触发RPA机器人的执行,并且支持在流程实例中查看RPA任务详情、RPA录屏和日志信息等。
亮点三:RPA流程的业务角色更直观
服务流程即业务集成流程,RPA节点作为其中一部分,可以直观地看到RPA如何在整条业务链中发挥作用。
相关链接
关于RPA集成的详细内容可参考下方链接:
RPA集成指南V1.0:
RPA节点录入币别到异构系统示例:
服务流程调用RPA视频:
划重点
1) 集成服务云提供了RPA集成功能。通过该功能,集成服务流程中可组装RPA机器人,在集成业务流程中实现标准化流程的自动执行。
2) 集成服务云的RPA集成具备以下特点: 查看全部
【爆款文章】集成服务与RPA的强强联袂,速来围观!
金蝶云·苍穹V5.0新特性系列文章持续更新
每工作日早上07:45准时推送
为你带来最新产品资讯
老师,业务上有个需求是把某异构系统的采购订单集成到星瀚系统中,可对方系统不提供接口,没办法获取数据啊。
我的想法是用RPA机器人自动登录该异构系统获取采购订单信息,保存到Excel文件里面,然后再到集成服务云使用离线导入功能手工上传文件,设置好字段映射关系之后加载到星瀚当中。
但手工设置费时费力,有没有办法让这个流程自动化完成而不需要人工参与呢?
开发小白
集成专家
很简单,服务流程最新发布特性支持配置RPA节点。只需稍微改造RPA机器人,让他多做一点事情(稍后揭晓),再把RPA组装到服务流程中即可实现RPA集成。
通过该方案,只需执行一个服务流程就能完成数据的获取、转换和加载步骤了~
听起来很棒,迫不及待想请老师指教了呢。
开发小白
集成专家
那就让我们赶紧开始吧!
适用版本
该功能适用版本为金蝶云·苍穹V4.0.016及以上。
特性展示
RPA是通过模拟用户界面操作的方式来完成自动化的一项技术。通过配置集成服务云服务流程的RPA节点,可以在集成服务中嵌入RPA流程,将集成云从各个系统获取到的数据信息作用于更广泛的业务场景。
接下来,小编将以某异构系统的采购订单集成到星瀚采购订单为例,为大家介绍服务流程如何实现RPA集成。

01 整体思路
通过RPA模拟前台操作,登录异构系统,打开采购订单,获取采购订单数据并上传Excel文件到FTP服务器中。
然后,集成服务云通过服务编排组装该RPA节点后,获取相应数据,其他节点进行数据的转换、映射和加载,完成后将执行结果通过通知节点短信通知到用户。
服务流程集成RPA案例示例
02实现步骤
步骤一:RPA控制台参数配置
使用该功能的前提是【系统服务云】→【配置工具】→【系统参数】中配置了RPA控制台的参数信息。如下图所示:
注:没有此参数请联系RPA部门提供。
RPA控制台系统集成参数
步骤二:用户授权与查看
配置服务流程的用户需要授权RPA通用角色。如下图所示:
RPA通用角色
完成角色授权后,登录RPA控制台即可查看当前用户可使用的RPA流程。如下图所示:
查看RPA流程
步骤三:设计RPA流程
RPA流程中,通过登录异构系统、打开采购订单、获取采购订单数据和保存数据并上传Excel文件四个子流程完成异构系统的采购订单信息的数据获取。
(注:此案例根据需求场景改造RPA流程,增加上传数据文件到FTP服务器子流程,非必选改造。)
RPA主流程 - 登录/打开采购订单/获取数据/上传
各子流程配置详情如下图所示:

左右滑动查看更多>>(从左至右依次为登录异构系统、打开采购订单、获取采购订单数据、保存并上传Excel文件)
步骤四:配置服务流程
创建服务流程后,在流程图中配置RPA节点,选择RPA流程和执行流程的RPA机器人。如下图所示:
服务流程中嵌入RPA节点
RPA节点详情
步骤五:运行监控
双击流程实例中的RPA节点,可查看RPA任务执行详情,如下图所示:
服务流程实例与RPA节点
RPA节点执行详情
亮点价值
亮点一:集成服务流程和RPA强强联合
打通集成服务云与RPA控制台,轻松实现异构系统的集成与标准化流程的执行、多渠道采集、加工与处理数据,提高工作效率,助力企业业务集成自动化。
亮点二:流程配置简单,执行过程可追溯
服务流程中可以直接配置一个或多个RPA节点,通过用户获取RPA流程与RPA机器人信息,在服务流程执行时自动触发RPA机器人的执行,并且支持在流程实例中查看RPA任务详情、RPA录屏和日志信息等。
亮点三:RPA流程的业务角色更直观
服务流程即业务集成流程,RPA节点作为其中一部分,可以直观地看到RPA如何在整条业务链中发挥作用。
相关链接
关于RPA集成的详细内容可参考下方链接:
RPA集成指南V1.0:
RPA节点录入币别到异构系统示例:
服务流程调用RPA视频:
划重点
1) 集成服务云提供了RPA集成功能。通过该功能,集成服务流程中可组装RPA机器人,在集成业务流程中实现标准化流程的自动执行。
2) 集成服务云的RPA集成具备以下特点:
红队从资产收集到打点
采集交流 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-08-12 04:28
最近想总结一下,在红队渗透拿到一个目标名或者刷src时候,怎么快速信息收集和批量检测来打到一个点,往往在实际项目中就是拼手速。
信息收集到打点大致我就分为
企业信息结构收集敏感信息收集域名主动被动收集整理域名ip资产扫描检测打点
其中每一步需要收集好几个方面的信息,手动很累也很慢
1.企业信息结构收集
企业信息结构收集包括对查询目标企业的公司信息,涉及到哪些主站域名,有哪些控股很多的子公司,这些子公司涉及到哪些域名,然后再进行备案反查,你又会得到一些新的公司,同理也能再次得到一些新的主站域名,将这些进行整理---->得到一批待爆破的域名。
还有的就是除了这些查到的主站域名,往往企业会有app、公众号、小程序这些资产,也要对这些资产进行收集,然后你又拿到了一批域名。
手动查询的话从以下查询
天眼查 查企业/子公司/域名/公众号 https://www.tianyancha.com/爱企查 https://aiqicha.baidu.com/企查查询 https://www.qcc.com/启信宝 https://www.qixin.com/
工具:
推荐cSubsidiary利用天眼查查询企业子公司https://github.com/canc3s/cSubsidiary<br />还有pigat:https://github.com/teamssix/pigat<br />公众号和app的收集:https://github.com/wgpsec/ENSc ... an_GO go版本
2.敏感信息收集
利用搜索引擎、github等托管平台配合一些dorks就可以搜到很多信息。
熟知的googlehack,gitdork,网盘泄露等等。
敏感信息一共要搜集这个几个方面:
googlehack语法github泄露目标人员姓名/手机/邮箱
1.googlehack
但比如googlehack,你需要搜的好几条语法加上域名
比如:
site:*.domain.cominurl:domain.comintitle:keywordkeyword filetyle:doc|pdf
一个域名可以配合多个语法搜,那么多域名手动输入搜很慢,推荐工具:
https://github.com/r00tSe7en/GoogleHackingTool 在线Google Hacking 小工具https://www.exploit-db.com/google-hacking-database 语法,自己可以脚本里批量搜
2.github泄露敏感信息:
一些常用github dorks,直接搜对应目标信息:
xxxxx.com "Authorization" #"Authorization: Bearer"xxxxx.com "filename:vim_settings.xml"xxxxx.com "language:PHP"
也可以在github对各种信息搜索,比如文件类型
filename:manifest.xmlfilename:travis.ymlfilename:vim_settings.xmlfilename:databasefilename:prod.exs NOT prod.secret.exsfilename:prod.secret.exsfilename:.npmrc _authfilename:.dockercfg authfilename:WebServers.xmlfilename:.bash_history filename:sftp-config.jsonfilename:sftp.json path:.vscodefilename:secrets.yml passwordfilename:.esmtprc passwordfilename:passwd path:etcfilename:dbeaver-data-sources.xmlpath:sites databases passwordfilename:config.php dbpasswdfilename:prod.secret.exsfilename:configuration.php JConfig passwordfilename:.sh_history
包含关键字的指定语言:
language:python usernamelanguage:php usernamelanguage:sql usernamelanguage:html passwordlanguage:perl passwordlanguage:shell usernamelanguage:java apiHOMEBREW_GITHUB_API_TOKEN language:shell
搜API/KEYS/TOEKNS关键字:
api_key“api keys”authorization_bearer:oauthauthauthenticationclient_secretapi_token:“api token”client_idpassworduser_passworduser_passpasscodeclient_secretsecretpassword hashOTPuser auth
很多关键字可以搜,还是批量搜高效,工具:
https://github.com/obheda12/Gi ... dorks
这类工具需要设置git令牌,附上gitrob过程,踩坑:不要下relase ,自己编译最好:
git clone https://github.com/michenriksen/gitrob.gitgo mod init #to use go mod 如果报错 运行go mod init github.com/michenriksen/gitrobrm Gopkg* #remove the old stuffgo build #to build it<br /><br />./build.sh
设置git令牌
set GITROB_ACCESS_TOKEN=xxxxx
使用后可以查看图形界面的结果:
3. 目标人员姓名/手机/邮箱
通过开源信息收集目标人员姓名/手机/邮箱,为后面打点做字典做准备。
https://github.com/laramies/theHarvester
通过搜索引擎、PGP服务器以及SHODAN数据库收集用户的email,子域名,主机,雇员名,开放端口和banner信息。
使用:
-d 开关用于定义域名,-l 用于限制结果数量
theHarvester -d kali.org -l 200 -banubis,baidu,pentesttools,projectdiscovery,qwant,rapiddns,rocketreach,securityTrails,spyse,sublist3r,threatcrowd,threatminer,trello,twitter,urlscan,virustotal,yahoo,zoomeye,bing,binaryedge,bingapi,bufferoverun,censys,certspotter,crtsh,dnsdumpster,duckduckgo,fullhunt,github-code,google,hackertarget,hunter,intelx,linkedin,linkedin_links,n45ht,omnisint,otx
按github跑就是了,但是有点坑点:
配置api-keys在/etc/theHarvester 目录下api-keys.yaml填入对应的api key即可
有个坑点是key:后要加个空格在放key字符串,不然跑不起来
人员邮箱字典的构造:
https://github.com/pry0cc/Goog ... ed.rb
还可以使用一些社工信息来做字典,这样的工具很多了,用一个就够了没必要用全部:Cupp/Cewl
https://github.com/r3nt0n/bopscrkpython3 bopscrk.py -i
3. 域名主动被动收集
域名主动信息收集内容就有点杂了。
通过1、2点我们拿到了一批等待爆破的域名和人员的信息,以及泄露的一些敏感信息(运气好的话用泄露的信息已经打到点了。)
现在需要对域名进行whois信息查询、dns域名正反查询、子域名探测爆破三个方面收集。
1.whois信息查询
whois需要查询域名的whois,然后根据whois信息来查询历史和反查,这样你就得到了一些邮箱和可疑域名。
查域名信息没什么说的,主要看网址注册人、到期记录、创建域的时间、名称服务器和联系信息等,查最新的一般都是托管的信息,而查看历史信息就有可能查到真实联系人邮箱电话等:
一些常见whois查询,手动的时候可以查询:
https://domaineye.com/reverse- ... whois
除了正向查询whois,还要查询whois历史信息:
以下几个网站允许用户访问连接的 WHOIS 数据库以进行调查。这些记录是十多年来对有关域注册的有用数据进行网络爬取的结果:
https://whois.domaintools.com/ ... .com/
whois历史信息查询不能错过,明显可以在whois历史信息中看真实邮箱并反查而不是目前托管的邮箱,以及非托管的dns服务器:
whois 信息反查
通过历史whois信息找到真实邮箱or组织名,再反查域名,又可以得到一批资产:
other:
https://www.reversewhois.io/
整理一下whois分了三步,先whois查询一个域名,然后对查询的信息进行历史whois查询和反查,最后得到一批邮箱和域名。手动知道过程就行,实际做项目用工具批量查了整理:
https://github.com/xugj-gits/domain-tool 批量whois查询https://github.com/melbadry9/WhoEnum
2.dns域名正向反向查询
dns域名查询分两个部分,历史记录和ip反查:
DNS历史记录(doamin2ips)
Dnsdumpster 是一个在线实用程序,我们使用它来查找子域、目标的 DNS 记录。
VT也是可以看dns数据信息的:
ip反查(ip2domains)
同ip查询多个解析到这个ip的域名,寻找更多web资产
https://viewdns.info/reverseip/
https://dnslytics.com/
ip反查也可以使用dig、nslookup、host命令完成:
工具推荐:
https://www.infobyip.com/ipbulklookup.php 批量ip反查https://github.com/Sma11New/ip2domain 国内域名推荐ip2domain,会查询权重、ICP备案等
通过dns查询,我们拿到了一些域名和可疑ip段
3.子域名探测爆破
没啥好说的,主要是收集的渠道全、过滤泛解析。
常见手法爆破子域名、证书透明度、搜索引擎、信息泄露、ASN号等等,很多工具已经做了这些工作
https://github.com/shmilylty/O ... redns
4. 整理域名ip资产
到这里大致的收集就结束了,就是要对收集结果进行整理,通过上面收集能拿到:
一批待探测存活的域名一批待确定的ip段一些邮箱,姓名,手机号一些敏感文件、信息、通用密码(敏感信息收集阶段看脸)
整理后大致如上,有一步需要做的就是把收集的这些域名,转成ip段,但是是需要判断这个ip属不属于cdn,属不属于泛解析的ip,然后转成ip后要判断ip段的权重,哪些段才可能是目标主要的C段。
https://github.com/EdgeSecurityTeam/Eeyes 对subdomain数据处理、获取其中真实IP并整理成c段https://github.com/canc3s/cIPR 整理后查看权重
5. 扫描检测打点
这步就开始快速打点了。
上面整理后的资产,需要我们探测的是一批域名和一批C段
域名需要做的事:
探测存活title、banner提取、指纹识别爬虫、目录轻量扫描、轻量漏扫
C段需要做的事:
扫描端口,探测存活将扫的web和非web进行分类,把扫到的web资产加入到域名需要做的事,和对待域名没区别将扫到的非web(数据库/远程登录协议)进行爆破,比如mysql爆破,rdp爆破
一批域名和一批C段就这样做不同的事,来先探测是否有脆弱的点,最后才是回归常规web,一个站一个站的去撕
一些工具:
https://github.com/broken5/WebAliveScan web存活判断https://github.com/fadinglr/EHole 红队重点攻击系统指纹探测工具https://github.com/k8gege/K8CScan 漏洞扫描、密码爆破https://github.com/b1gcat/DarkEye 主机发现+爆破https://github.com/Adminisme/ServerScan 高并发网络扫描、服务探测工具https://github.com/dean2021/titlesearch 批量抓取域名title工具https://github.com/pmiaowu/PmWebDirScan 批量扫目录备份
还有的就是一些大家都熟知的xray,vulmap之类的漏洞,批量轻量去扫描一下即可。
把上面的几个步骤,工具串起来,行成快速信息收集,快速探测打点,最好写个贯穿流程的工具调用的脚本,自己写过效果不错但代码不好就不拿出来丢人了,基本这样过一遍就容易打到一些比较脆弱的点。 查看全部
红队从资产收集到打点
最近想总结一下,在红队渗透拿到一个目标名或者刷src时候,怎么快速信息收集和批量检测来打到一个点,往往在实际项目中就是拼手速。
信息收集到打点大致我就分为
企业信息结构收集敏感信息收集域名主动被动收集整理域名ip资产扫描检测打点
其中每一步需要收集好几个方面的信息,手动很累也很慢
1.企业信息结构收集
企业信息结构收集包括对查询目标企业的公司信息,涉及到哪些主站域名,有哪些控股很多的子公司,这些子公司涉及到哪些域名,然后再进行备案反查,你又会得到一些新的公司,同理也能再次得到一些新的主站域名,将这些进行整理---->得到一批待爆破的域名。
还有的就是除了这些查到的主站域名,往往企业会有app、公众号、小程序这些资产,也要对这些资产进行收集,然后你又拿到了一批域名。
手动查询的话从以下查询
天眼查 查企业/子公司/域名/公众号 https://www.tianyancha.com/爱企查 https://aiqicha.baidu.com/企查查询 https://www.qcc.com/启信宝 https://www.qixin.com/
工具:
推荐cSubsidiary利用天眼查查询企业子公司https://github.com/canc3s/cSubsidiary<br />还有pigat:https://github.com/teamssix/pigat<br />公众号和app的收集:https://github.com/wgpsec/ENSc ... an_GO go版本
2.敏感信息收集
利用搜索引擎、github等托管平台配合一些dorks就可以搜到很多信息。
熟知的googlehack,gitdork,网盘泄露等等。
敏感信息一共要搜集这个几个方面:
googlehack语法github泄露目标人员姓名/手机/邮箱
1.googlehack
但比如googlehack,你需要搜的好几条语法加上域名
比如:
site:*.domain.cominurl:domain.comintitle:keywordkeyword filetyle:doc|pdf
一个域名可以配合多个语法搜,那么多域名手动输入搜很慢,推荐工具:
https://github.com/r00tSe7en/GoogleHackingTool 在线Google Hacking 小工具https://www.exploit-db.com/google-hacking-database 语法,自己可以脚本里批量搜
2.github泄露敏感信息:
一些常用github dorks,直接搜对应目标信息:
xxxxx.com "Authorization" #"Authorization: Bearer"xxxxx.com "filename:vim_settings.xml"xxxxx.com "language:PHP"
也可以在github对各种信息搜索,比如文件类型
filename:manifest.xmlfilename:travis.ymlfilename:vim_settings.xmlfilename:databasefilename:prod.exs NOT prod.secret.exsfilename:prod.secret.exsfilename:.npmrc _authfilename:.dockercfg authfilename:WebServers.xmlfilename:.bash_history filename:sftp-config.jsonfilename:sftp.json path:.vscodefilename:secrets.yml passwordfilename:.esmtprc passwordfilename:passwd path:etcfilename:dbeaver-data-sources.xmlpath:sites databases passwordfilename:config.php dbpasswdfilename:prod.secret.exsfilename:configuration.php JConfig passwordfilename:.sh_history
包含关键字的指定语言:
language:python usernamelanguage:php usernamelanguage:sql usernamelanguage:html passwordlanguage:perl passwordlanguage:shell usernamelanguage:java apiHOMEBREW_GITHUB_API_TOKEN language:shell
搜API/KEYS/TOEKNS关键字:
api_key“api keys”authorization_bearer:oauthauthauthenticationclient_secretapi_token:“api token”client_idpassworduser_passworduser_passpasscodeclient_secretsecretpassword hashOTPuser auth
很多关键字可以搜,还是批量搜高效,工具:
https://github.com/obheda12/Gi ... dorks
这类工具需要设置git令牌,附上gitrob过程,踩坑:不要下relase ,自己编译最好:
git clone https://github.com/michenriksen/gitrob.gitgo mod init #to use go mod 如果报错 运行go mod init github.com/michenriksen/gitrobrm Gopkg* #remove the old stuffgo build #to build it<br /><br />./build.sh
设置git令牌
set GITROB_ACCESS_TOKEN=xxxxx
使用后可以查看图形界面的结果:

3. 目标人员姓名/手机/邮箱
通过开源信息收集目标人员姓名/手机/邮箱,为后面打点做字典做准备。
https://github.com/laramies/theHarvester
通过搜索引擎、PGP服务器以及SHODAN数据库收集用户的email,子域名,主机,雇员名,开放端口和banner信息。
使用:
-d 开关用于定义域名,-l 用于限制结果数量
theHarvester -d kali.org -l 200 -banubis,baidu,pentesttools,projectdiscovery,qwant,rapiddns,rocketreach,securityTrails,spyse,sublist3r,threatcrowd,threatminer,trello,twitter,urlscan,virustotal,yahoo,zoomeye,bing,binaryedge,bingapi,bufferoverun,censys,certspotter,crtsh,dnsdumpster,duckduckgo,fullhunt,github-code,google,hackertarget,hunter,intelx,linkedin,linkedin_links,n45ht,omnisint,otx
按github跑就是了,但是有点坑点:
配置api-keys在/etc/theHarvester 目录下api-keys.yaml填入对应的api key即可
有个坑点是key:后要加个空格在放key字符串,不然跑不起来
人员邮箱字典的构造:
https://github.com/pry0cc/Goog ... ed.rb
还可以使用一些社工信息来做字典,这样的工具很多了,用一个就够了没必要用全部:Cupp/Cewl
https://github.com/r3nt0n/bopscrkpython3 bopscrk.py -i
3. 域名主动被动收集
域名主动信息收集内容就有点杂了。
通过1、2点我们拿到了一批等待爆破的域名和人员的信息,以及泄露的一些敏感信息(运气好的话用泄露的信息已经打到点了。)
现在需要对域名进行whois信息查询、dns域名正反查询、子域名探测爆破三个方面收集。
1.whois信息查询
whois需要查询域名的whois,然后根据whois信息来查询历史和反查,这样你就得到了一些邮箱和可疑域名。
查域名信息没什么说的,主要看网址注册人、到期记录、创建域的时间、名称服务器和联系信息等,查最新的一般都是托管的信息,而查看历史信息就有可能查到真实联系人邮箱电话等:
一些常见whois查询,手动的时候可以查询:
https://domaineye.com/reverse- ... whois
除了正向查询whois,还要查询whois历史信息:
以下几个网站允许用户访问连接的 WHOIS 数据库以进行调查。这些记录是十多年来对有关域注册的有用数据进行网络爬取的结果:
https://whois.domaintools.com/ ... .com/
whois历史信息查询不能错过,明显可以在whois历史信息中看真实邮箱并反查而不是目前托管的邮箱,以及非托管的dns服务器:
whois 信息反查
通过历史whois信息找到真实邮箱or组织名,再反查域名,又可以得到一批资产:
other:
https://www.reversewhois.io/
整理一下whois分了三步,先whois查询一个域名,然后对查询的信息进行历史whois查询和反查,最后得到一批邮箱和域名。手动知道过程就行,实际做项目用工具批量查了整理:
https://github.com/xugj-gits/domain-tool 批量whois查询https://github.com/melbadry9/WhoEnum

2.dns域名正向反向查询
dns域名查询分两个部分,历史记录和ip反查:
DNS历史记录(doamin2ips)
Dnsdumpster 是一个在线实用程序,我们使用它来查找子域、目标的 DNS 记录。
VT也是可以看dns数据信息的:
ip反查(ip2domains)
同ip查询多个解析到这个ip的域名,寻找更多web资产
https://viewdns.info/reverseip/
https://dnslytics.com/
ip反查也可以使用dig、nslookup、host命令完成:
工具推荐:
https://www.infobyip.com/ipbulklookup.php 批量ip反查https://github.com/Sma11New/ip2domain 国内域名推荐ip2domain,会查询权重、ICP备案等
通过dns查询,我们拿到了一些域名和可疑ip段
3.子域名探测爆破
没啥好说的,主要是收集的渠道全、过滤泛解析。
常见手法爆破子域名、证书透明度、搜索引擎、信息泄露、ASN号等等,很多工具已经做了这些工作
https://github.com/shmilylty/O ... redns
4. 整理域名ip资产
到这里大致的收集就结束了,就是要对收集结果进行整理,通过上面收集能拿到:
一批待探测存活的域名一批待确定的ip段一些邮箱,姓名,手机号一些敏感文件、信息、通用密码(敏感信息收集阶段看脸)
整理后大致如上,有一步需要做的就是把收集的这些域名,转成ip段,但是是需要判断这个ip属不属于cdn,属不属于泛解析的ip,然后转成ip后要判断ip段的权重,哪些段才可能是目标主要的C段。
https://github.com/EdgeSecurityTeam/Eeyes 对subdomain数据处理、获取其中真实IP并整理成c段https://github.com/canc3s/cIPR 整理后查看权重
5. 扫描检测打点
这步就开始快速打点了。
上面整理后的资产,需要我们探测的是一批域名和一批C段
域名需要做的事:
探测存活title、banner提取、指纹识别爬虫、目录轻量扫描、轻量漏扫
C段需要做的事:
扫描端口,探测存活将扫的web和非web进行分类,把扫到的web资产加入到域名需要做的事,和对待域名没区别将扫到的非web(数据库/远程登录协议)进行爆破,比如mysql爆破,rdp爆破
一批域名和一批C段就这样做不同的事,来先探测是否有脆弱的点,最后才是回归常规web,一个站一个站的去撕
一些工具:
https://github.com/broken5/WebAliveScan web存活判断https://github.com/fadinglr/EHole 红队重点攻击系统指纹探测工具https://github.com/k8gege/K8CScan 漏洞扫描、密码爆破https://github.com/b1gcat/DarkEye 主机发现+爆破https://github.com/Adminisme/ServerScan 高并发网络扫描、服务探测工具https://github.com/dean2021/titlesearch 批量抓取域名title工具https://github.com/pmiaowu/PmWebDirScan 批量扫目录备份
还有的就是一些大家都熟知的xray,vulmap之类的漏洞,批量轻量去扫描一下即可。
把上面的几个步骤,工具串起来,行成快速信息收集,快速探测打点,最好写个贯穿流程的工具调用的脚本,自己写过效果不错但代码不好就不拿出来丢人了,基本这样过一遍就容易打到一些比较脆弱的点。
文章采集调用 【】lotlib
采集交流 • 优采云 发表了文章 • 0 个评论 • 123 次浏览 • 2022-08-04 09:09
文章采集调用本地浏览器特定链接,ip就用转发地址好了。其他调用平台,一般也是接口上获取。各个视频站点可能使用smi等格式数据来互相传播,arctime支持。互联网时代,随着网速增快,付费会员日益增多,付费视频,付费文件所占比例在不断提高。
我不知道怎么用,但是我知道现在视频调试都是一个程序来监控的,
<p>importarctimeimportjsonimportmatplotlib。pyplotaspltimportnumpyasnp#获取官方文件大小,并转换成acrobat格式defget_ac_transactions(data_content):url=''returnr' 查看全部
文章采集调用 【】lotlib
文章采集调用本地浏览器特定链接,ip就用转发地址好了。其他调用平台,一般也是接口上获取。各个视频站点可能使用smi等格式数据来互相传播,arctime支持。互联网时代,随着网速增快,付费会员日益增多,付费视频,付费文件所占比例在不断提高。
我不知道怎么用,但是我知道现在视频调试都是一个程序来监控的,
<p>importarctimeimportjsonimportmatplotlib。pyplotaspltimportnumpyasnp#获取官方文件大小,并转换成acrobat格式defget_ac_transactions(data_content):url=''returnr'
天** | 使用selenium做数据采集
采集交流 • 优采云 发表了文章 • 0 个评论 • 133 次浏览 • 2022-07-30 11:09
哈工程管工在读博士,擅长数据采集&挖掘。
文末有代码可供下载
马云在接受CNBC(美国消费者新闻与商业频道)采访时提出:“整个世界将变成数据,我认为这还是只是数据时代的开始。新浪潮即将来临,很多就业机会将被夺走。有些人会赶上潮流,变得更加富有和成功。但是对于那些落后的人,未来将是痛苦的。”就小编看来,这种说法在人文社科研究当中也同样适用。在当前数以万计甚至数以十万计研究样本“遍地走”的时代,若我们还拘泥于传统的“小样本”研究(比如样本量为100多的调查问卷数据等),不仅难以跟随时代的脚步,还会逐渐丧失学术竞争力、从而被时代淘汰。那么,究竟该如何获取属于自己的大样本数据呢?今天小编就带大家用selenium库来爬取国内某知名第三方企业信息平台(天**)的企业工商信息。
一、自动打开网站页面
首先,数据爬取的第一步是利用selenium库启动浏览器,打开我们的目标网站。部分代码
# @Author : Jacob-ZHANG<br />import requests,base64<br />from PIL import Image<br />import csv,re<br />from selenium import webdriver<br />import time,random<br /><br />#1启动浏览器。<br />#win<br />browser=webdriver.Chrome(executable_path='driver/chromedriver.exe')<br />#mac<br />#browser=webdriver.Chrome(executable_path='driver/chromedriver')<br /><br />#2加入这个脚本可以避免被识别<br />browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br />"source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """ })<br /><br />#3延迟10s启动<br />browser.implicitly_wait(10)<br /><br />#4利用谷歌浏览器打开目标网页<br />browser.get('https://pro.xxxxxx.com/searchx')<br /><br />#5将窗口最大化<br />browser.maximize_window()<br /><br />#6给网页一些时间加载<br />time.sleep(random.randint(1, 2))<br />...<br />...<br />...<br />
非常简单,如下所示。
需要说明的是:
二、模拟登陆
天**反扒的第一关便是需要登录才能够查看具体的页面信息。相比于利用复杂JS逆向技术完成登陆而言,利用selenium库模拟人的操作、从而实现网站自动化登陆的做法则显得更为简单易行。从下图来看,我们需要利用selenium库来完成“点击密码登录(切换到密码登录页面,也即下图所示页面)-向账号对话框内输入账号-向密码对话框内输入密码-向验证码对话框内输入验证码-点击登录”等一系列操作后,才能登录到网站的信息页面,获取自己要想的数据。
对于网站的登录我们提供了以下两种方法:一种是自动化登录;另一种则是手动登录。
2.1 自动化登录
首先,我们先来看看较为复杂的自动化登录。要想实现网页的自动化登录,其关键在于利用 「外部力量」 来识别验证码并完成导入。具体而言,我们首先需要定位验证码在网页当中的元素位置,其次利用截图软件根据验证码元素位置来截取验证码图片,再次利用外部库对验证码图片进行识别,最后将识别出的验证码录入对话框。自动化登录的具体过程可以分为 get_code_image函数 和 parse_code函数 两个步骤进行,具体代码如下所示。其中,验证码的解析小编是调用了百度AI的开源库进行的。另外,需要注意的是,利用selenium库打开的登录页面一开始是不显示验证码的,必须向账号框和密码框输入内容以后,它才会显示验证码。因此,对于验证码的识别和录入,我们将它放在了所有操作中的最后部分。
parse_code函数
def parse_code():<br /> #用百度API解析图片<br /> request_url = "https://aip.baidubce.com/rest/ ... %3Bbr /> f = open('temp/验证码.png', 'rb')<br /> img = base64.b64encode(f.read())<br /> params = {"image": img}<br /> access_token = '24.a7fbbfb9dcab2e1054cc827f09d09234.2592000.1625930266.282335-19004069'<br /><br /> request_url = request_url + "?access_token=" + access_token<br /> headers = {'content-type': 'application/x-www-form-urlencoded'}<br /> response = requests.post(request_url, data=params, headers=headers)<br /> <br /> #得到解析结果<br /> dictionary=response.json()<br /> <br /> #得到验证码<br /> yanzhengma=dictionary['words_result'][0]['words']<br /> <br /> #录入验证码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/input').send_keys(yanzhengma)<br /> <br /> # 点击登录按钮<br /> time.sleep(random.randint(1, 2))<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[8]').click()<br /><br />
parse_code函数
def get_code_image():<br /> # 1启动浏览器。<br /> browser = webdriver.Chrome()<br /> # 2加入这个脚本可以避免被识别<br /> browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br /> "source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """})<br /> # 3延迟10s启动<br /> browser.implicitly_wait(10)<br /> <br /> # 4利用谷歌浏览器打开目标网页<br /> browser.get('https://pro.xxxxxx.com/searchx')<br /> <br /> # 5将窗口最大化<br /> browser.maximize_window()<br /> <br /> # 6给网页一些时间加载<br /> time.sleep(random.randint(1, 2))<br /><br /> #将页面从快捷登录切换到密码登录<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/div[1]/div[2]').click()<br /> time.sleep(0.5)<br /> <br /> #输入账号<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[2]/input').send_keys(<br /> '******')<br /> time.sleep(random.randint(1, 2))<br /> <br /> # 输入密码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[4]/input').send_keys('*******')<br /> time.sleep(random.randint(1, 2))<br /> browser.save_screenshot('temp/屏幕.png')#截图整个页面】<br /> <br /> #定位验证码x,y坐标<br /> left_angle=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img').location<br /> image=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img')<br /> <br /> #获取验证码的长和宽<br /> size=image.size<br /> <br /> #设定我们需要截取的位置<br /> rangle = (int(left_angle['x']), int(left_angle['y'] ), int(left_angle['x'] + size['width'] + 230),<br /><br /> int(left_angle['y'] + size['height'] + 300))<br /> <br /> #打开截图<br /> open_image=Image.open('temp/屏幕.png')<br /> <br /> #从图片中截取我们需要的的区域<br /> jietu=open_image.crop(rangle)<br /> jietu.save('temp/验证码.png')<br />
接下来,我们再来看看如何实现手动登录。相比于自动化登录,手动登录的操作更为简单。具体地,我们只要在完成自动打开网站页面的代码后加入input()函数,然后自己手动向网站的对话框内输入账号、密码、验证码并点击登录,就可以进入到网站的信息页面。
三、获取自己想要的数据
完成登陆之后,就会跳转到如下页面。然后,大家就可以根据自己的目标继续撰写属于自己的“个性化代码”了。下面,小编以获取31个省市的特定类型的企业数据为例,给大家分享一下自己获取数据的过程。
其实,代码撰写的逻辑很简单。首先要做的就是先选中我们要爬取的目标城市。下来就是根据自己的需求来定制个性化的筛选标准。以小编自己的需求为例,先通过点击高级模式,向企业名称对话框里输入关键词,比如医院(当然,大家也可以通过限定行业来挑选目标);然后,去掉机构类型中已勾选的企业,选择事业单位;接下来,勾选全部企业类型;最后点击查看结果。
至此就完成了筛选,得到了满足我们要求的所有企业(见下图1)。接下来,我们要做的就是遍历每一页里的每一家企业,然后获取企业页面信息(见下图2)中自己想要的数据了。
由于后续代码较长,就不在这里一一列举了。有需要的小伙伴可以在后台留言,然后向xx索取。
说明
对于初学者而言,直接上手可能比较难,建议先收藏本文,待熟练掌握爬虫可以实验本文的代码。
如果想复现本文代码,需熟悉
本文教程&代码免费分享,但作者时间和精力宝贵,可能无法做到一一指导,尽请包涵。
代码链接: 提取码: ob2j
精选文章
<p style="margin: 0px;padding: 0px;clear: both;min-height: 1em;outline: 0px;max-width: 100%;color: rgb(63, 63, 63);font-size: 15px;letter-spacing: 0px;white-space: normal;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;line-height: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;">长期招募小伙伴
从符号到嵌入:计算社会科学的两种文本表示<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
推荐 | 社科(经管)文本分析快速指南<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
使用cntext训练Glove词嵌入模型
cntext库 | 关于DUTIR被污染解决办法<br style="margin: 0px;padding: 0px;" />
EmoBank | 中文维度情感词典<br />
sklearnex库 | 两行代码百倍加速你的机器学习代码<br style="margin: 0px;padding: 0px;" />
认知的测量 | 向量距离vs语义投影
Wordify | 发现和区分消费者词汇的工具
karateclub库 | 计算社交网络中节点的向量
视频专栏课 | Python网络爬虫与文本分析
扩增内置pkl | 欢迎各位向cntext库分享情感词典<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
文本分析 | 中国企业高管团队创新注意力(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
LIWC vs Python | 文本分析之词典统计法略讲(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
PNAS | 文本网络分析&文化桥梁Python代码实现<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
Wordify | 发现和区分消费者词汇的工具
BERTopic库 | 使用预训练模型做话题建模
tomotopy | 速度最快的LDA主题模型
文本分析方法在《管理世界》(2021.5)中的应用
Wow~70G上市公司定期报告数据集
doccano|为机器学习建模做数据标注
使用WeasyPrint自动生成pdf报告文件
100min视频 | Python文本分析与会计
在jupyter内运行R代码</p> 查看全部
天** | 使用selenium做数据采集
哈工程管工在读博士,擅长数据采集&挖掘。
文末有代码可供下载
马云在接受CNBC(美国消费者新闻与商业频道)采访时提出:“整个世界将变成数据,我认为这还是只是数据时代的开始。新浪潮即将来临,很多就业机会将被夺走。有些人会赶上潮流,变得更加富有和成功。但是对于那些落后的人,未来将是痛苦的。”就小编看来,这种说法在人文社科研究当中也同样适用。在当前数以万计甚至数以十万计研究样本“遍地走”的时代,若我们还拘泥于传统的“小样本”研究(比如样本量为100多的调查问卷数据等),不仅难以跟随时代的脚步,还会逐渐丧失学术竞争力、从而被时代淘汰。那么,究竟该如何获取属于自己的大样本数据呢?今天小编就带大家用selenium库来爬取国内某知名第三方企业信息平台(天**)的企业工商信息。
一、自动打开网站页面
首先,数据爬取的第一步是利用selenium库启动浏览器,打开我们的目标网站。部分代码
# @Author : Jacob-ZHANG<br />import requests,base64<br />from PIL import Image<br />import csv,re<br />from selenium import webdriver<br />import time,random<br /><br />#1启动浏览器。<br />#win<br />browser=webdriver.Chrome(executable_path='driver/chromedriver.exe')<br />#mac<br />#browser=webdriver.Chrome(executable_path='driver/chromedriver')<br /><br />#2加入这个脚本可以避免被识别<br />browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br />"source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """ })<br /><br />#3延迟10s启动<br />browser.implicitly_wait(10)<br /><br />#4利用谷歌浏览器打开目标网页<br />browser.get('https://pro.xxxxxx.com/searchx')<br /><br />#5将窗口最大化<br />browser.maximize_window()<br /><br />#6给网页一些时间加载<br />time.sleep(random.randint(1, 2))<br />...<br />...<br />...<br />
非常简单,如下所示。
需要说明的是:
二、模拟登陆
天**反扒的第一关便是需要登录才能够查看具体的页面信息。相比于利用复杂JS逆向技术完成登陆而言,利用selenium库模拟人的操作、从而实现网站自动化登陆的做法则显得更为简单易行。从下图来看,我们需要利用selenium库来完成“点击密码登录(切换到密码登录页面,也即下图所示页面)-向账号对话框内输入账号-向密码对话框内输入密码-向验证码对话框内输入验证码-点击登录”等一系列操作后,才能登录到网站的信息页面,获取自己要想的数据。

对于网站的登录我们提供了以下两种方法:一种是自动化登录;另一种则是手动登录。
2.1 自动化登录
首先,我们先来看看较为复杂的自动化登录。要想实现网页的自动化登录,其关键在于利用 「外部力量」 来识别验证码并完成导入。具体而言,我们首先需要定位验证码在网页当中的元素位置,其次利用截图软件根据验证码元素位置来截取验证码图片,再次利用外部库对验证码图片进行识别,最后将识别出的验证码录入对话框。自动化登录的具体过程可以分为 get_code_image函数 和 parse_code函数 两个步骤进行,具体代码如下所示。其中,验证码的解析小编是调用了百度AI的开源库进行的。另外,需要注意的是,利用selenium库打开的登录页面一开始是不显示验证码的,必须向账号框和密码框输入内容以后,它才会显示验证码。因此,对于验证码的识别和录入,我们将它放在了所有操作中的最后部分。
parse_code函数
def parse_code():<br /> #用百度API解析图片<br /> request_url = "https://aip.baidubce.com/rest/ ... %3Bbr /> f = open('temp/验证码.png', 'rb')<br /> img = base64.b64encode(f.read())<br /> params = {"image": img}<br /> access_token = '24.a7fbbfb9dcab2e1054cc827f09d09234.2592000.1625930266.282335-19004069'<br /><br /> request_url = request_url + "?access_token=" + access_token<br /> headers = {'content-type': 'application/x-www-form-urlencoded'}<br /> response = requests.post(request_url, data=params, headers=headers)<br /> <br /> #得到解析结果<br /> dictionary=response.json()<br /> <br /> #得到验证码<br /> yanzhengma=dictionary['words_result'][0]['words']<br /> <br /> #录入验证码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/input').send_keys(yanzhengma)<br /> <br /> # 点击登录按钮<br /> time.sleep(random.randint(1, 2))<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[8]').click()<br /><br />
parse_code函数
def get_code_image():<br /> # 1启动浏览器。<br /> browser = webdriver.Chrome()<br /> # 2加入这个脚本可以避免被识别<br /> browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {<br /> "source": """<br /> Object.defineProperty(navigator, 'webdriver', {<br /> get: () => undefined<br /> })<br /> """})<br /> # 3延迟10s启动<br /> browser.implicitly_wait(10)<br /> <br /> # 4利用谷歌浏览器打开目标网页<br /> browser.get('https://pro.xxxxxx.com/searchx')<br /> <br /> # 5将窗口最大化<br /> browser.maximize_window()<br /> <br /> # 6给网页一些时间加载<br /> time.sleep(random.randint(1, 2))<br /><br /> #将页面从快捷登录切换到密码登录<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/div[1]/div[2]').click()<br /> time.sleep(0.5)<br /> <br /> #输入账号<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[2]/input').send_keys(<br /> '******')<br /> time.sleep(random.randint(1, 2))<br /> <br /> # 输入密码<br /> browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[4]/input').send_keys('*******')<br /> time.sleep(random.randint(1, 2))<br /> browser.save_screenshot('temp/屏幕.png')#截图整个页面】<br /> <br /> #定位验证码x,y坐标<br /> left_angle=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img').location<br /> image=browser.find_element_by_xpath('//*[@id="web-content"]/div/div[2]/div[3]/form/div[6]/img')<br /> <br /> #获取验证码的长和宽<br /> size=image.size<br /> <br /> #设定我们需要截取的位置<br /> rangle = (int(left_angle['x']), int(left_angle['y'] ), int(left_angle['x'] + size['width'] + 230),<br /><br /> int(left_angle['y'] + size['height'] + 300))<br /> <br /> #打开截图<br /> open_image=Image.open('temp/屏幕.png')<br /> <br /> #从图片中截取我们需要的的区域<br /> jietu=open_image.crop(rangle)<br /> jietu.save('temp/验证码.png')<br />
接下来,我们再来看看如何实现手动登录。相比于自动化登录,手动登录的操作更为简单。具体地,我们只要在完成自动打开网站页面的代码后加入input()函数,然后自己手动向网站的对话框内输入账号、密码、验证码并点击登录,就可以进入到网站的信息页面。
三、获取自己想要的数据
完成登陆之后,就会跳转到如下页面。然后,大家就可以根据自己的目标继续撰写属于自己的“个性化代码”了。下面,小编以获取31个省市的特定类型的企业数据为例,给大家分享一下自己获取数据的过程。

其实,代码撰写的逻辑很简单。首先要做的就是先选中我们要爬取的目标城市。下来就是根据自己的需求来定制个性化的筛选标准。以小编自己的需求为例,先通过点击高级模式,向企业名称对话框里输入关键词,比如医院(当然,大家也可以通过限定行业来挑选目标);然后,去掉机构类型中已勾选的企业,选择事业单位;接下来,勾选全部企业类型;最后点击查看结果。
至此就完成了筛选,得到了满足我们要求的所有企业(见下图1)。接下来,我们要做的就是遍历每一页里的每一家企业,然后获取企业页面信息(见下图2)中自己想要的数据了。
由于后续代码较长,就不在这里一一列举了。有需要的小伙伴可以在后台留言,然后向xx索取。
说明
对于初学者而言,直接上手可能比较难,建议先收藏本文,待熟练掌握爬虫可以实验本文的代码。
如果想复现本文代码,需熟悉
本文教程&代码免费分享,但作者时间和精力宝贵,可能无法做到一一指导,尽请包涵。
代码链接: 提取码: ob2j
精选文章
<p style="margin: 0px;padding: 0px;clear: both;min-height: 1em;outline: 0px;max-width: 100%;color: rgb(63, 63, 63);font-size: 15px;letter-spacing: 0px;white-space: normal;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;line-height: normal;box-sizing: border-box !important;overflow-wrap: break-word !important;">长期招募小伙伴
从符号到嵌入:计算社会科学的两种文本表示<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
推荐 | 社科(经管)文本分析快速指南<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
使用cntext训练Glove词嵌入模型
cntext库 | 关于DUTIR被污染解决办法<br style="margin: 0px;padding: 0px;" />
EmoBank | 中文维度情感词典<br />
sklearnex库 | 两行代码百倍加速你的机器学习代码<br style="margin: 0px;padding: 0px;" />
认知的测量 | 向量距离vs语义投影
Wordify | 发现和区分消费者词汇的工具
karateclub库 | 计算社交网络中节点的向量
视频专栏课 | Python网络爬虫与文本分析
扩增内置pkl | 欢迎各位向cntext库分享情感词典<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
文本分析 | 中国企业高管团队创新注意力(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
LIWC vs Python | 文本分析之词典统计法略讲(含代码)<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
PNAS | 文本网络分析&文化桥梁Python代码实现<br style="margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
Wordify | 发现和区分消费者词汇的工具
BERTopic库 | 使用预训练模型做话题建模
tomotopy | 速度最快的LDA主题模型
文本分析方法在《管理世界》(2021.5)中的应用
Wow~70G上市公司定期报告数据集
doccano|为机器学习建模做数据标注
使用WeasyPrint自动生成pdf报告文件
100min视频 | Python文本分析与会计
在jupyter内运行R代码</p>
文章采集调用 《抽奖》某次红蓝对抗之Solr-RCE实战绕过
采集交流 • 优采云 发表了文章 • 0 个评论 • 113 次浏览 • 2022-07-28 03:27
文章首发于:先知社区
前言
在某次红蓝对抗过程中。
要结束的时候突然发现了扫描器爆出了
Solr-RCE-CVE-2019-0192漏洞。
但是进行测试过程中,发现存在各种各样的问题
绕过1
进行测试,发现目标只可以执行单命令,返回字段比较少的命令。
输入:whoami、ipconfig
执行dir,无法执行。
不出网
想着直接执行ps直接上线就好了,各种尝试之后,后知后觉发现对方不出网
写websgell
发现目标不出网的时候,只有写webshell这一条路子可以走了。
但是目标只能执行个别命令还无法解决。
dir无法执行。陷入了沉思,加入单双引号也不行。
根据以前的内网经验是不是系统无法默认调用到dir.exe。
那么 cmd /c dir是不是可以。
惊奇的发现,可以完美的执行命令。
通过dir,找到目录。进行写马尝试。
但是发现目标路由规则写死了,无法直接访问到.jsp的文件。
刚开始以为是根目录的问题。
发现不止根目录,常用的css/js和img下面的也不行。
后续在webshell中看到,翻文件看到了有类似路由机制的验证
言归正传
在执行rce的时候,找到了solr的目录。发现这里的.jsp是没有这个验证的。
利用命令执行进行找到该位置,进行写文件。
问题来了。echo 写入一直无法写入。。
问题解决
把这里的特殊字符进行特殊字符编码,即可成功写入。
又遇到一个问题。jsp的马子都有%号,这里不论怎么做 %就是写不进去。
差点放弃。找不到不带%的马子。
柳暗花明
但是想到了上午利用过的Certutil可以进行编码解码
这样就没有特殊字符了。
完全没问题。
刚开始一点一点追加,发现下面的会写进去两行
看了一下就最后有一个+号,没有特殊字符。
全部直接写进去。
然后decode进行解码,完美。
访问但是是500.不过确很开心,因为确实写上来。
接下来解决为啥500就可以了。
type 123.jsp
查看一下。
发现最后decode的时候,少了一个>
本地测试,是没有这个问题的。可能是目标一次性字符长度的问题。
这里很简单了。
追加一下就可以了。
连接成功
验证
文末福利:
《安卓Frida逆向与协议分析》
本书翔实地介绍流行的Frida工具在安卓逆向工程中的应用,内容包括:如何安装和使用Frida、基本环境的搭建、Frida-tools、Frida脚本、Frida API、批量自动化Trace和分析、RPC远程方法调用、在无须逆向算法具体实现的情况下对Frida工具的调用,并提供了大量App逆向与协议分析案例,书中还介绍了更加稳定的Xposed框架的使用方法,以及从安卓源码开始定制属于自己的抓包沙箱,打造无法被绕过的抓包环境等内容。
本书案例丰富,注重实操,适合安卓应用安全工程师、安卓逆向分析工程师、爬虫工程师以及大数据采集和分析工程师使用。
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。
福利时间:7月25日-7月31日
2. 活动规则:
① 扫描下方二维码参与抽奖或公众号后台回复安卓逆向即可参与;
② 必要条件:转发本文到朋友圈,抽奖前不可删除;
③ 开奖结束后,请中奖小伙伴及时将中奖信息和朋友圈转发记录发送到公众号后台联系,超过24小时未领取的视为自动放弃哈!!!
未满足②条件但被抽中,则获奖资格会被取消哦
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。 查看全部
文章采集调用 《抽奖》某次红蓝对抗之Solr-RCE实战绕过
文章首发于:先知社区
前言
在某次红蓝对抗过程中。
要结束的时候突然发现了扫描器爆出了
Solr-RCE-CVE-2019-0192漏洞。
但是进行测试过程中,发现存在各种各样的问题
绕过1
进行测试,发现目标只可以执行单命令,返回字段比较少的命令。
输入:whoami、ipconfig
执行dir,无法执行。
不出网
想着直接执行ps直接上线就好了,各种尝试之后,后知后觉发现对方不出网
写websgell
发现目标不出网的时候,只有写webshell这一条路子可以走了。
但是目标只能执行个别命令还无法解决。
dir无法执行。陷入了沉思,加入单双引号也不行。
根据以前的内网经验是不是系统无法默认调用到dir.exe。
那么 cmd /c dir是不是可以。
惊奇的发现,可以完美的执行命令。
通过dir,找到目录。进行写马尝试。
但是发现目标路由规则写死了,无法直接访问到.jsp的文件。

刚开始以为是根目录的问题。
发现不止根目录,常用的css/js和img下面的也不行。
后续在webshell中看到,翻文件看到了有类似路由机制的验证
言归正传
在执行rce的时候,找到了solr的目录。发现这里的.jsp是没有这个验证的。
利用命令执行进行找到该位置,进行写文件。
问题来了。echo 写入一直无法写入。。
问题解决
把这里的特殊字符进行特殊字符编码,即可成功写入。
又遇到一个问题。jsp的马子都有%号,这里不论怎么做 %就是写不进去。
差点放弃。找不到不带%的马子。
柳暗花明
但是想到了上午利用过的Certutil可以进行编码解码
这样就没有特殊字符了。
完全没问题。
刚开始一点一点追加,发现下面的会写进去两行
看了一下就最后有一个+号,没有特殊字符。
全部直接写进去。
然后decode进行解码,完美。

访问但是是500.不过确很开心,因为确实写上来。
接下来解决为啥500就可以了。
type 123.jsp
查看一下。
发现最后decode的时候,少了一个>
本地测试,是没有这个问题的。可能是目标一次性字符长度的问题。
这里很简单了。
追加一下就可以了。
连接成功
验证
文末福利:
《安卓Frida逆向与协议分析》
本书翔实地介绍流行的Frida工具在安卓逆向工程中的应用,内容包括:如何安装和使用Frida、基本环境的搭建、Frida-tools、Frida脚本、Frida API、批量自动化Trace和分析、RPC远程方法调用、在无须逆向算法具体实现的情况下对Frida工具的调用,并提供了大量App逆向与协议分析案例,书中还介绍了更加稳定的Xposed框架的使用方法,以及从安卓源码开始定制属于自己的抓包沙箱,打造无法被绕过的抓包环境等内容。
本书案例丰富,注重实操,适合安卓应用安全工程师、安卓逆向分析工程师、爬虫工程师以及大数据采集和分析工程师使用。
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。
福利时间:7月25日-7月31日
2. 活动规则:
① 扫描下方二维码参与抽奖或公众号后台回复安卓逆向即可参与;
② 必要条件:转发本文到朋友圈,抽奖前不可删除;
③ 开奖结束后,请中奖小伙伴及时将中奖信息和朋友圈转发记录发送到公众号后台联系,超过24小时未领取的视为自动放弃哈!!!
未满足②条件但被抽中,则获奖资格会被取消哦
昨天文章抽奖图片有点问题,师傅们重新扫码再抽一波把。
社区精选 | 搭建前端监控,采集用户行为的 N 种姿势
采集交流 • 优采云 发表了文章 • 0 个评论 • 130 次浏览 • 2022-07-27 21:31
今天为各位带来的是社区作者杨成功的文章,在这篇文章中他为大家介绍了搭建前端监控,采集用户行为的 N 种姿势。
让我们一起来了解吧~
大家好,我是杨成功。
上一篇我们详细介绍了前端如何采集异常数据。采集异常数据是为了随时监测线上项目的运行情况,发现问题及时修复。在很多场景下,除了异常监控有用,收集用户的行为数据同样有意义。
怎么定义行为数据?顾名思义,就是用户在使用产品过程中产生的行为轨迹。比如去过哪几个页面,点过哪几个按钮,甚至在某个页面停留了多长时间,某个按钮点击了多少次,如果有需求都可以记录下来。
但是记录行为数据是一个和业务紧密关联的事情,不可能把每个用户每一步操作都极其详细的记录下来,这样会产生极其庞大的数据,很显然不现实。
合理的做法是,根据产品的实际情况评估,哪个模块哪个按钮需要重点记录,则可以采集的详细一些;哪些模块不需要重点关注,则简单记录一下基本信息。
根据这个逻辑,我们可以把行为数据分为两类:
通用数据特定数据
下面分别介绍这两类数据该如何收集。
通用数据
在一个产品中,用户最基本的行为就是切换页面。用户使用了哪些功能,也能从切换页面中体现出来。因此通用数据一般是在页面切换时产生,表示某个用户访问了某个页面。
页面切换对应到前端就是路由切换,可以通过监听路由变化来拿到新页面的数据。Vue 在全局路由守卫中监听路由变化,任意路由切换都能执行这里的回调函数。
// Vue3 路由写法<br />const router = createRouter({ ... })<br />router.beforeEach(to => {<br /> // to 代表新页面的路由对象<br /> recordBehaviors(to)<br />})<br />
React 在组件的 useEffect 中实现相同的功能。不过要注意一点,监听所有路由变化,则需要所有路由都经过这个组件,监听才有效果。具体的方法是配置路由时加*配置:
import HomePage from '@/pages/Home'<br />,<br />
然后在这个组件的的 useEffect 中监听路由变化:
// HomePage.jsx<br />const { pathname } = useLocation();<br />useEffect(() => {<br /> // 路由切换这个函数触发<br /> recordBehaviors(pathname);<br />}, [pathname]);<br />
上面代码中,在路由切换时都调用了recordBehaviors()方法并传入了参数。
Vue 传的是一个路由对象,React 传的是路由地址,接下来就可以在这个函数内收集数据了。
明确了在哪里收集数据,我们还要知道收集哪些数据。收集行为数据最基本的字段如下:
上面的字段中,应用标识、环境、版本号统称应用字段,用于标志数据的来源。其他字段主要分为用户,页面,时间三类,通过这三类数据就可以简单的判断出一件事:谁到过哪个页面,并停留了多长时间。
应用字段的配置和获取方式我们在上一节中讲过,就不做多余介绍了,获取字段的方式都是通用的。
下面介绍其他的几类数据如何获取。
获取用户信息
现代前端应用存储用户信息的方式基本都是一样的,localStorage 存一份,状态管理里存一份。因此获取用户信息从这两处的任意一处获得即可。这里简单介绍下如何从状态管理中获取。
最简单的方法,在函数recordBehaviors()所处的 js 文件中,直接导入用户状态:
// 从状态管理里中导出用户数据<br />import { UserStore } from '@/stores';<br />let { user_id, user_name } = UserStore;<br />
这里的@/stores 指向我项目中的文件 src/stores/index.ts,表示状态管理的入口文件,使用时替换成自己项目的实际位置。实际情况中还会有用户数据为空的问题,这里需要单独处理一下,方便我们在后续的数据查看中能看出分别:
import { UserStore } from '@/stores';<br /><br />// 收集行为函数<br />const recordBehaviors = ()=> {<br /> let report_date = {<br /> ...<br /> }<br /> if(UserStore) {<br /> let { user_id, user_name} = UserStore<br /> report_date.user_id = user_id || 0<br /> report_date.user_name = user_name || '未命名'<br /> } else {<br /> report_date.user_id = user_id || -1<br /> report_date.user_name = user_name || '未获取'<br /> }<br />}<br />
上面代码中,首先判断了状态管理中是否有用户数据,如果有则获取,没有则指定默认值。这里指定默认值的细节要注意,不是随便指定的,比如 user_id 的默认值有如下意义:
用户数据是经常容易出错的地方,因为涉及到登录状态和权限等复杂问题。指定了上述默认值后,就可以从收集到的行为数据中判断出某个页面用户状态是否正常。
获取页面信息
前面我们在监听路由变化的地方调用了 recordBehaviors 函数并传入了参数,页面信息可以从参数中拿到,我们先看在 Vue 中怎么获取:
// 路由配置<br />{<br /> path: '/test',<br /> meta: {<br /> title: '测试页面'<br /> },<br /> component: () => import('@/views/test/Index.vue')<br />}<br /><br />// 获取配置<br />const recordBehaviors = (to)=> {<br /> let page_route = to.path<br /> let page_title = to.meta.title<br />}<br />
Vue 中比较简单,可以直接从参数中拿到页面数据。相比之下,React 的参数只是一个路由地址,想拿到页面名称还需要做单独处理。
一般在设计权限时,我们会在服务端会维护一套路由数据,包含路由地址和名称。路由数据在登录后获取,存在状态管理中,那么有了 pathname 就可以从路由数据中找到对应的路由名称。
// React 中<br />import { RouteStore } from '@/stores';<br /><br />const recordBehaviors = (pathname) => {<br /> let { routers } = RouteStore; // 取出路由数据<br /> let route = routers.find((row) => (row.path = pathname));<br /> if (route) {<br /> let page_route = route.path;<br /> let page_title = route.title;<br /> }<br />};<br />
这样,页面信息的 page_route、page_title 两个字段也拿到了。
设置时间
行为数据中用两个字段start_at、end_at分别表示用户进入页面和离开页面的时间。这两个字段非常重要,我们在后续使用数据的时候可以判断出很多信息,比如:
还有很多信息,都能根据这两个时间字段判断。开始时间很好办,函数触发时直接获取当前时间:
<br />var start_at = new Date();<br />
结束时间这里需要考虑的情况比较多。首先要确定数据什么时候上报?用户进入页面后上报,还是离开页面时上报?
如果进入页面时上报,可以保证行为数据一定会被记录,不会丢失,但此时 end_at 字段必然为空。这样的话,就需要在离开页面时再调接口,将这条记录的 end_time 更新,这种方式的实现比较麻烦一些:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> http.post('/behaviors/insert', report_date).then(res=> {<br /> let id = res.id // 数据 id<br /> localStorage.setItem('CURRENT_BEHAVIOR_ID', id)<br /> })<br />}<br /><br />// 离开页面时调用:<br />const updateBehaviors = ()=> {<br /> let id = localStorage.getItem('CURRENT_BEHAVIOR_ID')<br /> let end_at = new Date()<br /> http.post('/behaviors/update/'+id, end_at) // 根据 id 更新结束时间<br /> localStorage.removeItem('CURRENT_BEHAVIOR_ID')<br />}<br />
上面代码中,进入页面先上报数据,并保存下 id,离开页面再根据 id 更新这条数据的结束时间。
如果在离开页面时上报,那么就要保证离开页面前上报接口已经触发,否则会导致数据丢失。在满足这个前提条件下,上报逻辑会变成这样:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> localStorage.setItem('CURRENT_BEHAVIOR', JSON.stringify(report_date));<br />}<br /><br />// 离开页面时调用<br />const reportBehaviors = () => {<br /> let end_at = new Date()<br /> let report_str = localStorage.getItem('CURRENT_BEHAVIOR')<br /> if(report_str) {<br /> let report_date = JSON.parse(report_str)<br /> report_date.end_at = end_at<br /> http.post('/behaviors/insert', report_date)<br /> } else {<br /> console.log('无行为数据')<br /> }<br />}<br />
对比一下这两种方案,第一种的弊端是接口需要调两次,这会使接口请求量倍增。第二种方案只调用一次,但是需要特别注意可靠性处理,总体来说第二种方案更好些。
特定数据
除了通用数据,大部分情况我们还要在具体的页面中收集某些特定的行为。比如某个关键的按钮有没有点击,点了多少次;或者某个关键区域用户有没有看到,看到(曝光)了多少次等等。
收集数据还有一个更专业的叫法 ———— 埋点。直观理解是,哪里需要上报数据,就埋一个上报函数进去。
通用数据针对所有页面自动收集,特定数据就需要根据每个页面的实际需求手动添加。以一个按钮为例:
点击;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
上面代码中,我们想记录这个按钮的点击情况,所以做了一个简单的埋点 ———— 在按钮点击事件中调用 repoerEvents()方法,这个方法内部会收集数据并上报。
这是最原始的埋点方式,直接将上报方法放到事件函数中。repoerEvents() 方法接收一个事件对象参数,在参数中获取需要上报的事件数据。
特定数据与通用数据的许多字段是一样的,收集特定数据需要的基本字段如下:
这些基本字段中,前 7 个字段与前面通用数据的获取完全一样,这里就不赘述了。实际上特定数据需要获取的专有字段只有 3 个:
这三个字段也非常容易获取。event_type 表示事件触发的类型,比如点击、滚动、拖动等,可以在事件对象中拿到。action_tag 和 action_label 是必须指定的属性,表示本次埋点的标识和文字描述,用于在后续的数据处理时方便查阅和统计。
了解了采集特定数据是怎么回事,接下来我们用代码实现。
手动埋点上报
假设要为登录按钮做埋点,按照上面的数据采集方式,我们书写代码如下:
<br /> 登录<br />;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
代码中,我们通过元素的自定义属性传递了 tag 和 label 两个标识,用于在上报函数中获取。
上报函数 repoerEvents() 代码逻辑如下:
// 埋点上报函数<br />const repoerEvents = (e)=> {<br /> let report_date = {...}<br /> let { tag, label } = e.target.dataset<br /> if(!tag || !label) {<br /> return new Error('上报元素属性缺失')<br /> }<br /> report_date.event_type = e.type<br /> report_date.action_tag = tag<br /> report_date.action_label = label<br /><br /> // 上报数据<br /> http.post('/events/insert', report_date)<br />}<br />
这样就实现了一个基本的特定数据埋点上报功能。
全局自动上报
现在我们回过头来梳理一下这个上报流程,虽然基本功能实现了,但是还有些不合理之处,比如:
首先我们的埋点方式是基于事件的,也就是说,不管元素本身是否需要事件处理,我们都要给他加上,并在函数内部调用 repoerEvents() 方法。如果一个项目需要埋点的地方非常多,这种方式的接入成本就会非常高。
参考之前做异常监控的逻辑,我们换一个思路:能否全局监听事件自动上报呢?
思考一下,如果要做全局监听事件,那么只能监听需要埋点的元素的事件。那么如何判断哪些元素需要埋点呢?
上面我们为埋点的元素指定了data-tag和data-label两个自定义属性,那是不是根据这两个自定义属性判断就可以?我们来试验一下:
window.addEventListener('click', (event) => {<br /> let { tag, label, trigger } = event.target.dataset;<br /> if (tag && label && trigger == 'click') {<br /> // 说明该元素需要埋点<br /> repoerEvents(event);<br /> }<br />});<br />
上面代码还多判断了一个自定义属性 dataset.trigger,表示元素在哪种事件触发时需要上报。全局监听事件需要这个标识,这样可避免事件冲突。
添加全局监听后,收集某个元素的特定数据就简单了,方法如下:
<br /> 保存<br /><br />
试验证明,上述全局处理的方式是可行的,这样的话就不需要在每一个元素上添加或修改事件处理函数了,只需要在元素中添加三个自定义属性 data-tag,data-label,data-trigger 就能自动实现数据埋点上报。
组件上报
上面全局监听事件上报的方式已经比手动埋点高效了许多,现在我们再换一个场景。
一般情况下当埋点功能成熟之后,会封装成一个 SDK 供其他项目使用。如果我们将采集数据按照 SDK 的思路实现,让开发者在全局监听事件,是不是一个好的方式呢?
显然是不太友好的。如果是一个 SDK,那么最好的方式是将所有内容聚合成一个组件,在组件内实现上报的所有功能,而不是让使用者在项目中添加监听事件。
封装组件的话,那么组件的功能最好是将要添加埋点的元素包裹,这样自定义元素也就不需要指定了,而转为组件的属性,然后在组件内实现事件监听。
以 React 为例,我们看一下如何将上面的采集功能封装为组件:
import { useEffect, useRef } from 'react';<br /><br />const CusReport = (props) => {<br /> const dom = useRef(null);<br /> const handelEvent = () => {<br /> console.log(props); // {tag:xx, label:xx, trigger:xx}<br /> repoerEvents(props);<br /> };<br /> useEffect(() => {<br /> if (dom.current instanceof HTMLElement) {<br /> dom.current.addEventListener(props.trigger, handelEvent);<br /> }<br /> }, []);<br /> return (<br /> <br /> {props.children}<br /> <br /> );<br />};<br /><br />export default CusReport;<br />
组件使用方式如下:
<br /> 测试<br /><br />
这样就比较优雅了,不需要修改目标元素,只要把组件包裹在目标元素之外即可。
总结
本文介绍了搭建前端监控如何采集行为数据,将数据分为通用数据和特定数据两个大类分别处理。同时也介绍了多种上报数据的方式,不同的场景可以选择不同的方式。
其中的数据部分只介绍了实现功能的基础字段,实际情况中可以根据自己的业务需求添加。
许多小伙伴留言这套前端监控能否开源,肯定是要开源的,不过内容比较多我还在做,等到基本完善了我会发一个版本,感谢小伙伴们的关注。
SegmentFault 思否社区小编说
自 2022-07-01 起 SegmentFault 思否公众号改版啦!之后将陆续推出新的栏目和大家见面!(请拭目以待呀~❤)
在「社区精选」栏目中,我们将为广大开发者推荐来自 SegmentFault 思否开发者社区的优质技术文章,这些文章全部出自社区中充满智慧的技术创作者哦!
希望通过这一栏目,大家可以共同学习技术干货,GET 新技能和各种花式技术小 Tips。
欢迎越来越多的开发者加入创作者的行列,我们将持续甄选出社区中优质的内容推介给更多人,让闪闪发光的技术创作者们走到聚光灯下,被更多人认识。
「社区精选」投稿邮箱:
投稿请附上社区文章地址 查看全部
社区精选 | 搭建前端监控,采集用户行为的 N 种姿势
今天为各位带来的是社区作者杨成功的文章,在这篇文章中他为大家介绍了搭建前端监控,采集用户行为的 N 种姿势。
让我们一起来了解吧~
大家好,我是杨成功。
上一篇我们详细介绍了前端如何采集异常数据。采集异常数据是为了随时监测线上项目的运行情况,发现问题及时修复。在很多场景下,除了异常监控有用,收集用户的行为数据同样有意义。
怎么定义行为数据?顾名思义,就是用户在使用产品过程中产生的行为轨迹。比如去过哪几个页面,点过哪几个按钮,甚至在某个页面停留了多长时间,某个按钮点击了多少次,如果有需求都可以记录下来。
但是记录行为数据是一个和业务紧密关联的事情,不可能把每个用户每一步操作都极其详细的记录下来,这样会产生极其庞大的数据,很显然不现实。
合理的做法是,根据产品的实际情况评估,哪个模块哪个按钮需要重点记录,则可以采集的详细一些;哪些模块不需要重点关注,则简单记录一下基本信息。
根据这个逻辑,我们可以把行为数据分为两类:
通用数据特定数据
下面分别介绍这两类数据该如何收集。
通用数据
在一个产品中,用户最基本的行为就是切换页面。用户使用了哪些功能,也能从切换页面中体现出来。因此通用数据一般是在页面切换时产生,表示某个用户访问了某个页面。
页面切换对应到前端就是路由切换,可以通过监听路由变化来拿到新页面的数据。Vue 在全局路由守卫中监听路由变化,任意路由切换都能执行这里的回调函数。
// Vue3 路由写法<br />const router = createRouter({ ... })<br />router.beforeEach(to => {<br /> // to 代表新页面的路由对象<br /> recordBehaviors(to)<br />})<br />
React 在组件的 useEffect 中实现相同的功能。不过要注意一点,监听所有路由变化,则需要所有路由都经过这个组件,监听才有效果。具体的方法是配置路由时加*配置:
import HomePage from '@/pages/Home'<br />,<br />
然后在这个组件的的 useEffect 中监听路由变化:
// HomePage.jsx<br />const { pathname } = useLocation();<br />useEffect(() => {<br /> // 路由切换这个函数触发<br /> recordBehaviors(pathname);<br />}, [pathname]);<br />
上面代码中,在路由切换时都调用了recordBehaviors()方法并传入了参数。
Vue 传的是一个路由对象,React 传的是路由地址,接下来就可以在这个函数内收集数据了。
明确了在哪里收集数据,我们还要知道收集哪些数据。收集行为数据最基本的字段如下:
上面的字段中,应用标识、环境、版本号统称应用字段,用于标志数据的来源。其他字段主要分为用户,页面,时间三类,通过这三类数据就可以简单的判断出一件事:谁到过哪个页面,并停留了多长时间。
应用字段的配置和获取方式我们在上一节中讲过,就不做多余介绍了,获取字段的方式都是通用的。
下面介绍其他的几类数据如何获取。
获取用户信息
现代前端应用存储用户信息的方式基本都是一样的,localStorage 存一份,状态管理里存一份。因此获取用户信息从这两处的任意一处获得即可。这里简单介绍下如何从状态管理中获取。
最简单的方法,在函数recordBehaviors()所处的 js 文件中,直接导入用户状态:
// 从状态管理里中导出用户数据<br />import { UserStore } from '@/stores';<br />let { user_id, user_name } = UserStore;<br />
这里的@/stores 指向我项目中的文件 src/stores/index.ts,表示状态管理的入口文件,使用时替换成自己项目的实际位置。实际情况中还会有用户数据为空的问题,这里需要单独处理一下,方便我们在后续的数据查看中能看出分别:
import { UserStore } from '@/stores';<br /><br />// 收集行为函数<br />const recordBehaviors = ()=> {<br /> let report_date = {<br /> ...<br /> }<br /> if(UserStore) {<br /> let { user_id, user_name} = UserStore<br /> report_date.user_id = user_id || 0<br /> report_date.user_name = user_name || '未命名'<br /> } else {<br /> report_date.user_id = user_id || -1<br /> report_date.user_name = user_name || '未获取'<br /> }<br />}<br />
上面代码中,首先判断了状态管理中是否有用户数据,如果有则获取,没有则指定默认值。这里指定默认值的细节要注意,不是随便指定的,比如 user_id 的默认值有如下意义:
用户数据是经常容易出错的地方,因为涉及到登录状态和权限等复杂问题。指定了上述默认值后,就可以从收集到的行为数据中判断出某个页面用户状态是否正常。

获取页面信息
前面我们在监听路由变化的地方调用了 recordBehaviors 函数并传入了参数,页面信息可以从参数中拿到,我们先看在 Vue 中怎么获取:
// 路由配置<br />{<br /> path: '/test',<br /> meta: {<br /> title: '测试页面'<br /> },<br /> component: () => import('@/views/test/Index.vue')<br />}<br /><br />// 获取配置<br />const recordBehaviors = (to)=> {<br /> let page_route = to.path<br /> let page_title = to.meta.title<br />}<br />
Vue 中比较简单,可以直接从参数中拿到页面数据。相比之下,React 的参数只是一个路由地址,想拿到页面名称还需要做单独处理。
一般在设计权限时,我们会在服务端会维护一套路由数据,包含路由地址和名称。路由数据在登录后获取,存在状态管理中,那么有了 pathname 就可以从路由数据中找到对应的路由名称。
// React 中<br />import { RouteStore } from '@/stores';<br /><br />const recordBehaviors = (pathname) => {<br /> let { routers } = RouteStore; // 取出路由数据<br /> let route = routers.find((row) => (row.path = pathname));<br /> if (route) {<br /> let page_route = route.path;<br /> let page_title = route.title;<br /> }<br />};<br />
这样,页面信息的 page_route、page_title 两个字段也拿到了。
设置时间
行为数据中用两个字段start_at、end_at分别表示用户进入页面和离开页面的时间。这两个字段非常重要,我们在后续使用数据的时候可以判断出很多信息,比如:
还有很多信息,都能根据这两个时间字段判断。开始时间很好办,函数触发时直接获取当前时间:
<br />var start_at = new Date();<br />
结束时间这里需要考虑的情况比较多。首先要确定数据什么时候上报?用户进入页面后上报,还是离开页面时上报?
如果进入页面时上报,可以保证行为数据一定会被记录,不会丢失,但此时 end_at 字段必然为空。这样的话,就需要在离开页面时再调接口,将这条记录的 end_time 更新,这种方式的实现比较麻烦一些:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> http.post('/behaviors/insert', report_date).then(res=> {<br /> let id = res.id // 数据 id<br /> localStorage.setItem('CURRENT_BEHAVIOR_ID', id)<br /> })<br />}<br /><br />// 离开页面时调用:<br />const updateBehaviors = ()=> {<br /> let id = localStorage.getItem('CURRENT_BEHAVIOR_ID')<br /> let end_at = new Date()<br /> http.post('/behaviors/update/'+id, end_at) // 根据 id 更新结束时间<br /> localStorage.removeItem('CURRENT_BEHAVIOR_ID')<br />}<br />
上面代码中,进入页面先上报数据,并保存下 id,离开页面再根据 id 更新这条数据的结束时间。
如果在离开页面时上报,那么就要保证离开页面前上报接口已经触发,否则会导致数据丢失。在满足这个前提条件下,上报逻辑会变成这样:
// 进入页面时调用<br />const recordBehaviors = () => {<br /> let report_date = {...} // 此时 end_at 为空<br /> localStorage.setItem('CURRENT_BEHAVIOR', JSON.stringify(report_date));<br />}<br /><br />// 离开页面时调用<br />const reportBehaviors = () => {<br /> let end_at = new Date()<br /> let report_str = localStorage.getItem('CURRENT_BEHAVIOR')<br /> if(report_str) {<br /> let report_date = JSON.parse(report_str)<br /> report_date.end_at = end_at<br /> http.post('/behaviors/insert', report_date)<br /> } else {<br /> console.log('无行为数据')<br /> }<br />}<br />
对比一下这两种方案,第一种的弊端是接口需要调两次,这会使接口请求量倍增。第二种方案只调用一次,但是需要特别注意可靠性处理,总体来说第二种方案更好些。
特定数据
除了通用数据,大部分情况我们还要在具体的页面中收集某些特定的行为。比如某个关键的按钮有没有点击,点了多少次;或者某个关键区域用户有没有看到,看到(曝光)了多少次等等。
收集数据还有一个更专业的叫法 ———— 埋点。直观理解是,哪里需要上报数据,就埋一个上报函数进去。
通用数据针对所有页面自动收集,特定数据就需要根据每个页面的实际需求手动添加。以一个按钮为例:
点击;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
上面代码中,我们想记录这个按钮的点击情况,所以做了一个简单的埋点 ———— 在按钮点击事件中调用 repoerEvents()方法,这个方法内部会收集数据并上报。
这是最原始的埋点方式,直接将上报方法放到事件函数中。repoerEvents() 方法接收一个事件对象参数,在参数中获取需要上报的事件数据。
特定数据与通用数据的许多字段是一样的,收集特定数据需要的基本字段如下:
这些基本字段中,前 7 个字段与前面通用数据的获取完全一样,这里就不赘述了。实际上特定数据需要获取的专有字段只有 3 个:
这三个字段也非常容易获取。event_type 表示事件触发的类型,比如点击、滚动、拖动等,可以在事件对象中拿到。action_tag 和 action_label 是必须指定的属性,表示本次埋点的标识和文字描述,用于在后续的数据处理时方便查阅和统计。
了解了采集特定数据是怎么回事,接下来我们用代码实现。
手动埋点上报
假设要为登录按钮做埋点,按照上面的数据采集方式,我们书写代码如下:
<br /> 登录<br />;<br />const onClick = (e) => {<br /> // console.log(e);<br /> repoerEvents(e);<br />};<br />
代码中,我们通过元素的自定义属性传递了 tag 和 label 两个标识,用于在上报函数中获取。
上报函数 repoerEvents() 代码逻辑如下:

// 埋点上报函数<br />const repoerEvents = (e)=> {<br /> let report_date = {...}<br /> let { tag, label } = e.target.dataset<br /> if(!tag || !label) {<br /> return new Error('上报元素属性缺失')<br /> }<br /> report_date.event_type = e.type<br /> report_date.action_tag = tag<br /> report_date.action_label = label<br /><br /> // 上报数据<br /> http.post('/events/insert', report_date)<br />}<br />
这样就实现了一个基本的特定数据埋点上报功能。
全局自动上报
现在我们回过头来梳理一下这个上报流程,虽然基本功能实现了,但是还有些不合理之处,比如:
首先我们的埋点方式是基于事件的,也就是说,不管元素本身是否需要事件处理,我们都要给他加上,并在函数内部调用 repoerEvents() 方法。如果一个项目需要埋点的地方非常多,这种方式的接入成本就会非常高。
参考之前做异常监控的逻辑,我们换一个思路:能否全局监听事件自动上报呢?
思考一下,如果要做全局监听事件,那么只能监听需要埋点的元素的事件。那么如何判断哪些元素需要埋点呢?
上面我们为埋点的元素指定了data-tag和data-label两个自定义属性,那是不是根据这两个自定义属性判断就可以?我们来试验一下:
window.addEventListener('click', (event) => {<br /> let { tag, label, trigger } = event.target.dataset;<br /> if (tag && label && trigger == 'click') {<br /> // 说明该元素需要埋点<br /> repoerEvents(event);<br /> }<br />});<br />
上面代码还多判断了一个自定义属性 dataset.trigger,表示元素在哪种事件触发时需要上报。全局监听事件需要这个标识,这样可避免事件冲突。
添加全局监听后,收集某个元素的特定数据就简单了,方法如下:
<br /> 保存<br /><br />
试验证明,上述全局处理的方式是可行的,这样的话就不需要在每一个元素上添加或修改事件处理函数了,只需要在元素中添加三个自定义属性 data-tag,data-label,data-trigger 就能自动实现数据埋点上报。
组件上报
上面全局监听事件上报的方式已经比手动埋点高效了许多,现在我们再换一个场景。
一般情况下当埋点功能成熟之后,会封装成一个 SDK 供其他项目使用。如果我们将采集数据按照 SDK 的思路实现,让开发者在全局监听事件,是不是一个好的方式呢?
显然是不太友好的。如果是一个 SDK,那么最好的方式是将所有内容聚合成一个组件,在组件内实现上报的所有功能,而不是让使用者在项目中添加监听事件。
封装组件的话,那么组件的功能最好是将要添加埋点的元素包裹,这样自定义元素也就不需要指定了,而转为组件的属性,然后在组件内实现事件监听。
以 React 为例,我们看一下如何将上面的采集功能封装为组件:
import { useEffect, useRef } from 'react';<br /><br />const CusReport = (props) => {<br /> const dom = useRef(null);<br /> const handelEvent = () => {<br /> console.log(props); // {tag:xx, label:xx, trigger:xx}<br /> repoerEvents(props);<br /> };<br /> useEffect(() => {<br /> if (dom.current instanceof HTMLElement) {<br /> dom.current.addEventListener(props.trigger, handelEvent);<br /> }<br /> }, []);<br /> return (<br /> <br /> {props.children}<br /> <br /> );<br />};<br /><br />export default CusReport;<br />
组件使用方式如下:
<br /> 测试<br /><br />
这样就比较优雅了,不需要修改目标元素,只要把组件包裹在目标元素之外即可。
总结
本文介绍了搭建前端监控如何采集行为数据,将数据分为通用数据和特定数据两个大类分别处理。同时也介绍了多种上报数据的方式,不同的场景可以选择不同的方式。
其中的数据部分只介绍了实现功能的基础字段,实际情况中可以根据自己的业务需求添加。
许多小伙伴留言这套前端监控能否开源,肯定是要开源的,不过内容比较多我还在做,等到基本完善了我会发一个版本,感谢小伙伴们的关注。
SegmentFault 思否社区小编说
自 2022-07-01 起 SegmentFault 思否公众号改版啦!之后将陆续推出新的栏目和大家见面!(请拭目以待呀~❤)
在「社区精选」栏目中,我们将为广大开发者推荐来自 SegmentFault 思否开发者社区的优质技术文章,这些文章全部出自社区中充满智慧的技术创作者哦!
希望通过这一栏目,大家可以共同学习技术干货,GET 新技能和各种花式技术小 Tips。
欢迎越来越多的开发者加入创作者的行列,我们将持续甄选出社区中优质的内容推介给更多人,让闪闪发光的技术创作者们走到聚光灯下,被更多人认识。
「社区精选」投稿邮箱:
投稿请附上社区文章地址
实战 | SRC信息收集思路总结
采集交流 • 优采云 发表了文章 • 0 个评论 • 72 次浏览 • 2022-06-18 20:11
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。以下均为小弟拙见,大佬勿喷。
0x01 确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可与从中选择自己感兴趣的SRC。
0x02 确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
0x03 子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
0x04 系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
0x05 框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
0x06 非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
0x07 端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
0x08 目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
0x09 JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
0xA小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
0xB总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,以上内容均为小弟拙见,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
0XC 推荐一个网站
有SRC的厂商列表,可以自己去专属的SRC提交漏洞
查看全部
实战 | SRC信息收集思路总结
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。以下均为小弟拙见,大佬勿喷。
0x01 确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可与从中选择自己感兴趣的SRC。
0x02 确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
0x03 子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
0x04 系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
0x05 框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
0x06 非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
0x07 端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
0x08 目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
0x09 JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
0xA小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
0xB总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,以上内容均为小弟拙见,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
0XC 推荐一个网站
有SRC的厂商列表,可以自己去专属的SRC提交漏洞
SRC信息收集思路分享,很有用!
采集交流 • 优采云 发表了文章 • 0 个评论 • 77 次浏览 • 2022-06-18 15:01
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。
确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。
所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。关联阅读:
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可以从中选择自己感兴趣的SRC。
确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。
里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。
下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass(现在无法使用,勿下载)
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。
那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。
经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。
说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?
不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。
例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
https://github.com/Threezh1/JSFinder
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。
个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。
来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。
下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。
小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。
也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。
所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
声明:本公众号所分享内容仅用于网安爱好者之间的技术讨论,禁止用于违法途径,所有渗透都需获取授权!否则需自行承担,本公众号及原作者不承担相应的后果.
@学习更多渗透技能!体验靶场实战练习
<p style="outline: 0px;letter-spacing: 0.544px;">(hack视频资料及工具)<br style="outline: 0px;" />
</p>
(部分展示)
往期推荐
看到这里了,点个“赞”、“再看”吧 查看全部
SRC信息收集思路分享,很有用!
说到信息收集,网上已经有许多文章进行描述了,那么从正常的子域名、端口、旁站、C段等进行信息收集的话,对于正常项目已经够用了,但是挖掘SRC的话,在诸多竞争对手的“帮助”下,大家收集到的信息都差不多,挖掘的漏洞也往往存在重复的情况。
那么现在我就想分享一下平时自己进行SRC挖掘过程中,主要是如何进行入手的。
确定目标
无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘到漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。挖到的大多数是大家都可以简单挖到的漏洞,存在大概率重复可能。
所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。
那么目标怎么选呢,考虑到收益回报与付出的比例来看,建议是从专属SRC入手,特别在一些活动中,可以获取比平时更高的收益。关联阅读:
微信搜一搜:
百度搜一搜:
现在有活动的src已经浮现水面了,那么我们就可以从中选择自己感兴趣的SRC。
确认测试范围
前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
1、公众号
从公众号推文入手,活动页面中可以发现测试范围
2、应急响应官网
在应急响应官网,往往会有一些活动的公告,在里面可以获取到相应的测试范围。
3、爱企查
从爱企查等商业查询平台获取公司所属域名
搜索想要测试等SRC所属公司名称,在知识产权->网站备案中可以获取测试范围。
子域名(oneforall)
拿到域名之后,下一步我考虑使用oneforall扫描获取子域名,就像网上信息收集的文章一样,主域名的站点不是静态界面就是安全防护等级极强,不是随便就能够发现漏洞的,我们挖掘SRC也是要从子域名开始,从边缘资产或一般资产中发现漏洞。
工具下载:
https://github.com/shmilylty/OneForAll
具体用法如下:
常用的获取子域名有2种选择,一种使用--target指定单个域名,一种使用--targets指定域名文件。
python3 oneforall.py --target example.com run<br />python3 oneforall.py --targets ./domains.txt run
其他获取子域名的工具还有layer子域名挖掘机、Sublist3r、证书透明度、在线工具等,这里就不一一阐述了,大体思路是一样等,获取子域,然后从中筛选边缘资产,安全防护低资产。
系统指纹探测
通过上面的方法,我们可以在/OneForAll-0.4.3/results/路径下获取以域名为名字的csv文件。
里面放入到便是扫描到到所有子域名以及相应信息了。
下一步便是将收集到到域名全部进行一遍指纹探测,从中找出一些明显使用CMS、OA系统、shiro、Fastjson等的站点。
下面介绍平时使用的2款工具:
1、Ehole
下载地址:
https://github.com/EdgeSecurityTeam/EHole
使用方法:
./Ehole-darwin -l url.txt //URL地址需带上协议,每行一个<br />./Ehole-darwin -f 192.168.1.1/24 //支持单IP或IP段,fofa识别需要配置fofa密钥和邮箱<br />./Ehole-darwin -l url.txt -json export.json //结果输出至export.json文件
2、Glass(现在无法使用,勿下载)
下载地址:
https://github.com/s7ckTeam/Glass
使用方法:
python3 Glass.py -u http://www.examples.com // 单url测试<br />python3 Glass.py -w domain.txt -o 1.txt // url文件内
框架型站点漏洞测试
前面经过了子域名收集以及对收集到的子域名进行了指纹信息识别之后,那么对于框架型的站点,我们可以优先进行测试。
类似用友NC、通达OA、蓝凌OA等,可以通过尝试现有的Nday漏洞进行攻击。
非框架型站点漏洞测试
前面测试完框架型的站点了,之后就应该往正常网站,或者经过了二开未能直接检测出指纹的站点进行渗透了。
那么对于这类站点,最经常遇到的便是登录框,在这里,我们便可以开始测试了。
1、用户名枚举
抓包尝试是否用户名存在与不存在的情况,返回结果不同。
2、验证码
是否存在验证码,验证码是否可以抓包截断绕过,验证码是否可以为空。
3、暴力破解
下面是我收集的集中常见的用户名
1.弱口令用户名如admin,test,ceshi等<br />2.员工姓名全拼,员工姓名简拼<br />3.公司特征+员工工号/员工姓名<br />4.员工工号+姓名简拼<br />5.员工姓名全拼+员工工号<br />6.员工姓名全拼+重复次数,如zhangsan和zhangsan01<br />7.其他
关于暴力破解我要扯一句了,就是关于密码字典的问题。
经常会听到某人说他的字典多么多么的大,有好几个G之类的,但是在我觉得,这很没有必要,有些密码是你跑几天都跑不出来的,就算字典确实够大,也没有必要这样跑,可能影响心情不说,大规模地暴力破解,很容易让人觉得你在拒绝服务攻击。
其实我的话一般跑一跑弱口令就差不多了。
关于弱口令字典的问题,我也想说一嘴,你最好看看,你字典里面的admin、123456、password处在什么位置。记得之前玩CTF的时候,默认密码123456,但是那个师傅死活做不出来,后面一看,字典里面居然没有123456这个密码。。。
这里推荐一个字典,个人感觉还是挺好用的。当然更多的是需要自己不断更新。
https://github.com/fuzz-security/SuperWordlist
4、工具cupp和cewl
对于一些情况,密码不是直接使用弱口令,而是通过一些公司的特征+个人信息制作的,那么这个时候,我们的字典便不能直接使用了,需要在这之前加上一些特征,例如阿里SRC可能是a;百度SRC可能是bd等。
下面2款kali自带等工具,可以通过收集信息,生成好用的字典,方便渗透。
说真的,在渗透测试过程中,弱口令,YYDS!
具体使用说明和工具介绍,可以查看文章:
5、自行注册
如果能够注册那就好办了,自己注册一下账户即可。
6、小总结
对于非框架的站点,登录接口一般是必不可少的,可能就在主页,也可能在某个路径下,藏着后台的登录接口,在尝试了多种方法成功登录之后,记得尝试里面是否存在未授权漏洞、越权等漏洞。
这里借用来自WS师傅的建议:可以直接扫描出来的洞,基本都被交完了,可以更多往逻辑漏洞方面找。登录后的漏洞重复率,比登录前的往往会低很多。
端口扫描
前面就是正常的渗透了,那么一个域名只是在80、443端口才有web服务吗?
不可否认有些时候真的是,但是绝大多数情况下,类似8080、8443、8081、8089、7001等端口,往往会有惊喜哦~
端口扫描也算是老生常谈了,市面上也有很多介绍端口扫描的工具使用方法,这里也不细说了,就放出平时使用的命令吧。
sudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v examples.comsudo nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 examples.com
目录扫描dirsearch
目录扫描在渗透测试过程中我认为是必不可少的,一个站点在不同目录下的不同文件,往往可能有惊喜哦。
个人是喜欢使用dirserach这款工具,不仅高效、页面也好看。市面上还有例如御剑、御剑t00ls版等,也是不错的选择。
dirsearch下载地址:
https://github.com/maurosoria/dirsearch
具体使用方法可以查看github介绍,这里我一般是使用如下命令(因为担心线程太高所以通过-t参数设置为2)
python3 dirsearch.py -u www.xxx.com -e * -t 2
关键的地方是大家都可以下载这款工具,获取它自带的字典,那么路径的话,便是大家都能够搜得到的了,所以这里我推荐是可以适当整合一些师傅们发出来的路径字典到/dirsearch-0.4.2/db/dicc.txt中。
例如我的话,是增加了springboot未授权的一些路径、swagger的路径以及一些例如vmvare-vcenter的漏洞路径。
JS信息收集
在一个站点扫描了目录、尝试登录失败并且没有自己注册功能的情况下,我们还可以从JS文件入手,获取一些URL,也许某个URL便能够未授权访问获取敏感信息呢。
1、JSFinder
工具下载:
https://github.com/Threezh1/JSFinder
JSFinder是一款用作快速在网站的js文件中提取URL,子域名的工具。
个人觉得美中不足的地方便是不能对获取到到URL进行一些过滤,在某些情况下,JS文件中可以爬取非常多的URL,这其中可能大部分是页面空或者返回200但是页面显示404的。
来自HZ师傅的建议,可以修改一下工具,基于当前的基础上,检测获取的URL是否可以访问,访问后的页面大小为多少,标题是什么。。。
思路放这了,找个时间改一改?
#检测URL状态码#-----------------------#! /usr/bin/env python#coding=utf-8import sysimport requestsurl='xxxx'request = requests.get(url)httpStatusCode = request.status_codeif httpStatusCode == 200: xxxxelse: xxxx
#检测URL返回包大小#-----------------------import requestsdef hum_convert(value): units = ["B", "KB", "MB", "GB", "TB", "PB"] size = 1024.0 for i in range(len(units)): if (value / size) < 1: return "%.2f%s" % (value, units[i]) value = value / sizer = requests.get('https://www.baidu.com')r.status_coder.headerslength = len(r.text)print(hum_convert(length))
#获取网站标题#-----------------------#!/usr/bin/python#coding=utf-8urllib.requestimport urllib.requestimport reurl = urllib.request.urlopen('http://www.xxx.com')html = url.read().decode('utf-8')title=re.findall('(.+)',html)print (title)
2、JS文件
JS文件与HTML、CSS等文件统一作为前端文件,是可以通过浏览器访问到的,相对于HTML和CSS等文件的显示和美化作用,JS文件将会能够将页面的功能点进行升华。
对于渗透测试来说,JS文件不仅仅能够找到一些URL、内网IP地址、手机号、调用的组件版本等信息,还存在一些接口,因为前端需要,所以一些接口将会在JS文件中直接或间接呈现。
下面我将介绍如何发现这些隐藏的接口。
1、首先在某个页面中,鼠标右键,选择检查
2、点击Application
3、在Frames->top->Scripts中能够获取当前页面请求到的所有JS
4、火狐浏览器的话,则是在调试中
5、如果你请求的JS文件内容都叠在了前几行的话,下面这个键可以帮你美化输出
6、在JS文件中,可以尤为注意带有api字眼的文件或内容,例如下面这里我发现了一个接口。
小程序、APP
web端没有思路的时候,可以结合小程序、APP来进行渗透。
小程序或APP的服务端其实可以在一定程度上与web应用的服务端相联系。
也就是说,我们在小程序或者APP上,一样能够挖掘web端的漏洞如SQL注入、XSS等,并且相对来说,这类等服务端安全措施会相对没有那么完备,所以在web端确实没有思路的时候,可以迂回渗透,从小程序、APP中进行。
#小程序抓包、APP抓包参考链接:<br />https://mp.weixin.qq.com/s/xuo ... %3Bbr />https://mp.weixin.qq.com/s/45Y ... %3Bbr />https://mp.weixin.qq.com/s/M5x ... %3Bbr />https://mp.weixin.qq.com/s/Mfkbxtrxv5AvY-n_bMU7ig
总结
以上就是我个人挖掘SRC的一些信息收集思路,挖掘SRC有的时候真的很看运气,也许别人对一个接口简单Fuzz,便出了一个注入,而我们花了几天,还是一直看到返回内容为404。
所以有的时候真的可以换个站试试,也许就挖到高危甚至严重了~
作为一名SRC小白,希望能够通过这篇文章,帮到更多的网络安全小白,没能帮上大佬们真的很抱歉~后续也会持续提高自己,将学到的更多的东西分享给大家。
声明:本公众号所分享内容仅用于网安爱好者之间的技术讨论,禁止用于违法途径,所有渗透都需获取授权!否则需自行承担,本公众号及原作者不承担相应的后果.
@学习更多渗透技能!体验靶场实战练习
<p style="outline: 0px;letter-spacing: 0.544px;">(hack视频资料及工具)<br style="outline: 0px;" />
(部分展示)
往期推荐
看到这里了,点个“赞”、“再看”吧
文章采集excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~
采集交流 • 优采云 发表了文章 • 0 个评论 • 129 次浏览 • 2022-06-09 14:19
文章采集调用excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~自动刷新banner、喜欢题目后推荐的手游、相应游戏的收入大幅增长~欢迎关注公众号并与本人交流~【长按扫码加入游戏帝国数据交流群】群名称:数据魔方收集题目:哪些网站比百度更懂中国互联网?将通过采集不同维度的数据,去分析中国互联网的发展和中国互联网游戏的发展趋势。
这次采集的数据集是智能问答领域的相关数据。数据集量为22631条,包含512个样本。每条样本包含30个问题,554个标签。这次的目标是分析一下2010年至今国内大多数站点上的语音通话,也包括大量没有开放数据的,例如通话能力不达标准的站点。预计耗时3-5天。数据集说明数据样本:本数据集已经对问题,标签以及语音频率进行了pre处理,每条问题后面使用逗号间隔隔开。
读取数据时需要选择以weka或java版本的python库,手机端的java在数据读取阶段发生了错误,大约耗时30秒。我尝试在kaggle的googleanalytics中数据导入,然后再使用kaggle自带的googleloggersspark套件导入数据集,但最终结果并不好。代码数据格式:youtu.be/i17alvfog提取方式:以字典格式读取youtu.be/kgcolor_readerkcbxt提取数据的关键字段fname提取数据所在位置:数据读取工具:pipinstallpandaspandasimportpandasaspd#读取获取数据pandasread_csv('数据集.csv',index=true)#将数据集变换成dataframe格式csv_data=pd.read_csv(pandas.dataframe(fname=fname))#读取数据集head=csv_data.head(5)#数据集概览title1=csv_data.title2=csv_data.title3=csv_data.title4=csv_data.title5=csv_data.title6=csv_data.title7=csv_data.title8=csv_data.title9=csv_data.title10=csv_data.title11=csv_data.title12=csv_data.title13=csv_data.title14=csv_data.title15=csv_data.title16=csv_data.title17=csv_data.title18=csv_data.title19=csv_data.title20=csv_data.title21=csv_data.title22=csv_data.title23=csv_data.title24=csv_data.title25=csv_data.title26=csv_data.title27=csv_data.title28=csv_data.title29。 查看全部
文章采集excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~
文章采集调用excel模型分析,接下来进行数据的提取、数据可视化相关内容的学习和进阶~自动刷新banner、喜欢题目后推荐的手游、相应游戏的收入大幅增长~欢迎关注公众号并与本人交流~【长按扫码加入游戏帝国数据交流群】群名称:数据魔方收集题目:哪些网站比百度更懂中国互联网?将通过采集不同维度的数据,去分析中国互联网的发展和中国互联网游戏的发展趋势。
这次采集的数据集是智能问答领域的相关数据。数据集量为22631条,包含512个样本。每条样本包含30个问题,554个标签。这次的目标是分析一下2010年至今国内大多数站点上的语音通话,也包括大量没有开放数据的,例如通话能力不达标准的站点。预计耗时3-5天。数据集说明数据样本:本数据集已经对问题,标签以及语音频率进行了pre处理,每条问题后面使用逗号间隔隔开。
读取数据时需要选择以weka或java版本的python库,手机端的java在数据读取阶段发生了错误,大约耗时30秒。我尝试在kaggle的googleanalytics中数据导入,然后再使用kaggle自带的googleloggersspark套件导入数据集,但最终结果并不好。代码数据格式:youtu.be/i17alvfog提取方式:以字典格式读取youtu.be/kgcolor_readerkcbxt提取数据的关键字段fname提取数据所在位置:数据读取工具:pipinstallpandaspandasimportpandasaspd#读取获取数据pandasread_csv('数据集.csv',index=true)#将数据集变换成dataframe格式csv_data=pd.read_csv(pandas.dataframe(fname=fname))#读取数据集head=csv_data.head(5)#数据集概览title1=csv_data.title2=csv_data.title3=csv_data.title4=csv_data.title5=csv_data.title6=csv_data.title7=csv_data.title8=csv_data.title9=csv_data.title10=csv_data.title11=csv_data.title12=csv_data.title13=csv_data.title14=csv_data.title15=csv_data.title16=csv_data.title17=csv_data.title18=csv_data.title19=csv_data.title20=csv_data.title21=csv_data.title22=csv_data.title23=csv_data.title24=csv_data.title25=csv_data.title26=csv_data.title27=csv_data.title28=csv_data.title29。
文章采集调用googleapi,估计能免费获取到文章的几篇文章
采集交流 • 优采云 发表了文章 • 0 个评论 • 113 次浏览 • 2022-05-22 16:06
文章采集调用googleapi,估计能免费获取到文章的几篇文章,但注意看好文章!!!推荐小黑胖收集整理的。那个都能看到,只是bug多。要不你弄弄这个,一篇文章都能看到。其他平台应该也行。但能不能一次采集到整个链接,就是另一回事了。需要用到文章收集软件。把需要收集的文章都收集起来,重点是还是一次性收集好后不能再次收集的,在返回收集文章的软件就行。
需要注册一个谷歌账号,
采集云,一键登录,
如果是简单的博客爬虫,可以直接下载相关api文档即可。googleapidocs需要翻墙,很多博客爬虫都可以参考。
googlereader.
freegradleweekly
api,github上有很多开源的githubapis,都能抓googleapi;scrapy,scrapy是scrapy框架开发的,可以抓googleapi;httplib,可以抓googleapi,不用翻墙;用python爬虫先看googleapi,googleapi没有你想象的那么复杂,网上有例子,实践下就知道了。
推荐apifinder。而且目前来说,
googleapidocs
newmail-weeklyextension
apifinder
有使用过,也有之前从网上找到的api,googleapisnewsreader这些,都大同小异。 查看全部
文章采集调用googleapi,估计能免费获取到文章的几篇文章
文章采集调用googleapi,估计能免费获取到文章的几篇文章,但注意看好文章!!!推荐小黑胖收集整理的。那个都能看到,只是bug多。要不你弄弄这个,一篇文章都能看到。其他平台应该也行。但能不能一次采集到整个链接,就是另一回事了。需要用到文章收集软件。把需要收集的文章都收集起来,重点是还是一次性收集好后不能再次收集的,在返回收集文章的软件就行。
需要注册一个谷歌账号,
采集云,一键登录,
如果是简单的博客爬虫,可以直接下载相关api文档即可。googleapidocs需要翻墙,很多博客爬虫都可以参考。
googlereader.
freegradleweekly
api,github上有很多开源的githubapis,都能抓googleapi;scrapy,scrapy是scrapy框架开发的,可以抓googleapi;httplib,可以抓googleapi,不用翻墙;用python爬虫先看googleapi,googleapi没有你想象的那么复杂,网上有例子,实践下就知道了。
推荐apifinder。而且目前来说,
googleapidocs
newmail-weeklyextension
apifinder
有使用过,也有之前从网上找到的api,googleapisnewsreader这些,都大同小异。
【文末赠书】红队攻防之信息收集总结
采集交流 • 优采云 发表了文章 • 0 个评论 • 85 次浏览 • 2022-05-15 14:33
之前也总结过类似的信息收集相关的文章,但是每隔一段时间理解和手法都会有所不同,本文以hvv或授权但仅提供公司名称域名等情况下渗透测试的视角总结一些自己最近做信息收集的流程套路。
信息收集一、初始已知信息
前言中提到的两种情况,一般初始信息只有公司名称、个别官网域名、靶标名称等信息,以此为起点进行信息收集。
二、搜寻根域名
此步骤个人的经验是,面对大公司优先选择工信部备案查询,小公司用搜索引擎做起点,然后几种方式都可以过一遍,查漏补缺,尽量获取最全的信息。大部分公司根域名都不会很多,全部过一遍也不会用掉多少时间。
1.搜索引擎
搜索引擎直接搜索其公司名称,获取其相关根域名
2.天眼查、企查查
从天眼查、企查查等途径,输入公司名,查询其域名以及全资控股子公司的域名
3.工信部备案
工信部备案查询域名/ip地址(需要详细且正确的公司名称,结果也会很全面)
#/Integrated/recordQuery
4.fofa
fofa查询其公司名称,获取相关域名
5.站长之家
使用其icp查询功能查询备案,当我们不知道公司完整名称的时候也可以使用此网站功能使用已知域名查询完整备案公司名称
6.反查域名
用已知的某些ip反查域名
三、子域名
在子域名收集这步本人一般不喜欢爆破的方式,子域名爆破比较依赖字典,字典小就收集不全,字典大就很费时间,所以一般优先在各类解析记录的网站查询。
1.各类网站查询解析记录
以bilibili为例:
类似的网站非常多,这两个都是免费的,但是第二个要注册登录
2.子域名爆破
相关的工具很多,部分扫描器也自带子域名爆破功能或可安装相关插件。
subDomainsBrute
3.fofa、shodan
利用这类工具对域名资产进行查询,如
fofa语法domain=””
4.OneForAll
此工具会集成多种方式搜集子域名,包括dns查询、证书查询等,详情见其项目中的readme
安装
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />3<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />4<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />5<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
git clone https://github.com/shmilylty/OneForAll.git<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />cd OneForAll/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 -m pip install -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --help<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
python3 oneforall.py --target example.com run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --targets ./example.txt run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
四、ip
ip列表不完全来源于域名解析,有一部分ip是直接使用ip地址提供服务的,需要提前收集这部分信息,另一部分是通过域名解析过来的。
1.各类网站查询解析记录
同子域名查询中的操作,但是需要做的是把ip列表导出
2.解析域名
将所有已收集到的子域名通过脚本批量调用dig或nslookup解析ip
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
nslookup xxx.com<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />dig xxx.com @114.114.114.114<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
编写脚本批量调用dig命令,导出结果
或将域名列表放在在线解析网站中,导出其解析结果
这个步骤中需要额外关注cdn的情况,绕过cdn寻找其真实ip,可参考这篇文档
3.c段
将前面已经获得的ip全部整理好,使用脚本进行排序,懒得写脚本也可以使用在线的功能
如ip地址排序计算器
得到排序好的ip,可以先自己判断哪些c段可能属于目标,再进行一些扫描和访问,整理更全面的ip列表。
五、端口
使用masscan、nmap等工具对端口信息进行收集
六、web服务
使用webfinder等工具扫描已整理ip列表的web常用端口,导出形如:port/以及:port/的web服务列表
指纹识别
1.
2.
七、漏扫1.主机扫描
上文整理好的ip列表和域名列表,可以丢入主机扫描相关的扫描器中,如goby、Nessus等
2.web扫描
整理好web服务列表,可以丢入awvs等工具进行扫描,同时可以联动xray批量扫描 查看全部
【文末赠书】红队攻防之信息收集总结
之前也总结过类似的信息收集相关的文章,但是每隔一段时间理解和手法都会有所不同,本文以hvv或授权但仅提供公司名称域名等情况下渗透测试的视角总结一些自己最近做信息收集的流程套路。
信息收集一、初始已知信息
前言中提到的两种情况,一般初始信息只有公司名称、个别官网域名、靶标名称等信息,以此为起点进行信息收集。
二、搜寻根域名
此步骤个人的经验是,面对大公司优先选择工信部备案查询,小公司用搜索引擎做起点,然后几种方式都可以过一遍,查漏补缺,尽量获取最全的信息。大部分公司根域名都不会很多,全部过一遍也不会用掉多少时间。
1.搜索引擎
搜索引擎直接搜索其公司名称,获取其相关根域名
2.天眼查、企查查
从天眼查、企查查等途径,输入公司名,查询其域名以及全资控股子公司的域名
3.工信部备案
工信部备案查询域名/ip地址(需要详细且正确的公司名称,结果也会很全面)
#/Integrated/recordQuery
4.fofa
fofa查询其公司名称,获取相关域名
5.站长之家
使用其icp查询功能查询备案,当我们不知道公司完整名称的时候也可以使用此网站功能使用已知域名查询完整备案公司名称
6.反查域名
用已知的某些ip反查域名
三、子域名
在子域名收集这步本人一般不喜欢爆破的方式,子域名爆破比较依赖字典,字典小就收集不全,字典大就很费时间,所以一般优先在各类解析记录的网站查询。
1.各类网站查询解析记录
以bilibili为例:
类似的网站非常多,这两个都是免费的,但是第二个要注册登录
2.子域名爆破
相关的工具很多,部分扫描器也自带子域名爆破功能或可安装相关插件。
subDomainsBrute
3.fofa、shodan
利用这类工具对域名资产进行查询,如
fofa语法domain=””
4.OneForAll
此工具会集成多种方式搜集子域名,包括dns查询、证书查询等,详情见其项目中的readme
安装
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />3<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />4<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />5<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
git clone https://github.com/shmilylty/OneForAll.git<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />cd OneForAll/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 -m pip install -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --help<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
python3 oneforall.py --target example.com run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />python3 oneforall.py --targets ./example.txt run<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
四、ip
ip列表不完全来源于域名解析,有一部分ip是直接使用ip地址提供服务的,需要提前收集这部分信息,另一部分是通过域名解析过来的。
1.各类网站查询解析记录
同子域名查询中的操作,但是需要做的是把ip列表导出
2.解析域名
将所有已收集到的子域名通过脚本批量调用dig或nslookup解析ip
1<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />2<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
nslookup xxx.com<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />dig xxx.com @114.114.114.114<br style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;" />
编写脚本批量调用dig命令,导出结果
或将域名列表放在在线解析网站中,导出其解析结果
这个步骤中需要额外关注cdn的情况,绕过cdn寻找其真实ip,可参考这篇文档
3.c段
将前面已经获得的ip全部整理好,使用脚本进行排序,懒得写脚本也可以使用在线的功能
如ip地址排序计算器
得到排序好的ip,可以先自己判断哪些c段可能属于目标,再进行一些扫描和访问,整理更全面的ip列表。
五、端口
使用masscan、nmap等工具对端口信息进行收集
六、web服务
使用webfinder等工具扫描已整理ip列表的web常用端口,导出形如:port/以及:port/的web服务列表
指纹识别
1.
2.
七、漏扫1.主机扫描
上文整理好的ip列表和域名列表,可以丢入主机扫描相关的扫描器中,如goby、Nessus等
2.web扫描
整理好web服务列表,可以丢入awvs等工具进行扫描,同时可以联动xray批量扫描
一篇文章教会你利用Python网络爬虫实现豆瓣电影采集
采集交流 • 优采云 发表了文章 • 0 个评论 • 199 次浏览 • 2022-05-14 11:03
点击上方“IT共享之家”,进行关注
回复“资料”可获赠Python学习福利
【一、项目背景】
豆瓣电影提供最新的电影介绍及评论包括上映影片的影讯查询及购票服务。可以记录想看、在看和看过的电影电视剧 、顺便打分、写影评。极大地方便了人们的生活。
今天以电视剧(美剧)为例,批量爬取对应的电影,写入csv文档 。用户可以通过评分,更好的选择自己想要的电影。
【二、项目目标】
获取对应的电影名称,评分,详情链接,下载 电影的图片,保存文档。
【三、涉及的库和网站】
1、网址如下:
https://movie.douban.com/j/sea ... rt%3D{}
2、涉及的库:requests、fake_useragent、json、csv
3、软件:PyCharm
【四、项目分析】
1、如何多网页请求?
点击下一页时,每增加一页paged自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
2. 如何获取真正请求的地址?
请求数据时,发现页面上并没有对应数据。其实豆瓣网采用javascript动态加载内容,防止采集。
1)F12右键检查,找到Network,左边菜单Name , 找到第五个数据,点击Preview。
2)点开subjects,可以看到 title 就是对应电影名称。rate就是对应评分。通过js解析subjects字典,找到需要的字段。
3. 如何网页访问?
https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3D60
当点击下一页时,每增加一页page自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
【五、项目实施】
1、我们定义一个class类继承object,然后定义init方法继承self,再定义一个主函数main继承self。导入需要的库和请求网址。
import requests,jsonfrom fake_useragent import UserAgentimport csv<br />class Doban(object): def __init__(self): self.url = "https://movie.douban.com/j/sea ... rt%3D{}"<br /> def main(self): pass<br />if __name__ == '__main__': Siper = Doban() Siper.main()
2、随机产生UserAgent,构造请求头,防止反爬。
for i in range(1, 50): self.headers = { 'User-Agent': ua.random, }
3、发送请求 ,获取响应,页面回调,方便下次请求。
def get_page(self, url): res = requests.get(url=url, headers=self.headers) html = res.content.decode("utf-8") return html
4、json解析页面数据,获取对应的字典。
data = json.loads(html)['subjects'] # print(data[0])
5、for遍历,获取对应的电影名、 评分、下详情页链接。
print(name, goblin_herf) html2 = self.get_page(goblin_herf) # 第二个发生请求 parse_html2 = etree.HTML(html2) r = parse_html2.xpath('//div[@class="entry"]/p/text()')
6、创建csv文件进行写入,定义对应的标题头内容,保存数据 。
# 创建csv文件进行写入 csv_file = open('scr.csv', 'a', encoding='gbk') csv_writer = csv.writer(csv_file) # 写入csv标题头内容 csv_writerr.writerow(['电影', '评分', "详情页"]) #写入数据 csv_writer.writerow([id, rate, urll])
7、图片地址进行请求。定义图片名称,保存文档。
html2 = requests.get(url=urll, headers=self.headers).content dirname = "./图/" + id + ".jpg" with open(dirname, 'wb') as f: f.write(html2) print("%s 【下载成功!!!!】" % id)
8、调用方法,实现功能。
html = self.get_page(url) self.parse_page(html)
9、项目优化:1)设置时间延时。
time.sleep(1.4)
2)定义一个变量u, for遍历,表示爬取的是第几页。(更清晰可观)。
u = 0 self.u += 1;
【六、效果展示】
1、点击绿色小三角运行输入起始页,终止页( 从0页开始 )。
2、将下载成功信息显示在控制台。
3、保存csv文档。
4、电影图片展示。
【七、总结】
1、不建议抓取太多数据,容易对服务器造成负载,浅尝辄止即可。
2、本文章就Python爬取豆瓣网,在应用中出现的难点和重点,以及如何防止反爬,做出了相对于的解决方案。
3、希望通过这个项目,能够帮助了解json解析页面的基本流程,字符串是如何拼接,format函数如何运用。
4、本文基于Python网络爬虫,利用爬虫库,实现豆瓣电影及其图片的获取。实现的时候,总会有各种各样的问题,切勿眼高手低,勤动手,才可以理解的更加深刻。
5、需要本文源码的小伙伴,请在下方公众号后台回复“豆瓣电影”四个字,即可获取。
看完本文有收获?请转发分享给更多的人
IT共享之家 查看全部
一篇文章教会你利用Python网络爬虫实现豆瓣电影采集
点击上方“IT共享之家”,进行关注
回复“资料”可获赠Python学习福利
【一、项目背景】
豆瓣电影提供最新的电影介绍及评论包括上映影片的影讯查询及购票服务。可以记录想看、在看和看过的电影电视剧 、顺便打分、写影评。极大地方便了人们的生活。
今天以电视剧(美剧)为例,批量爬取对应的电影,写入csv文档 。用户可以通过评分,更好的选择自己想要的电影。
【二、项目目标】
获取对应的电影名称,评分,详情链接,下载 电影的图片,保存文档。
【三、涉及的库和网站】
1、网址如下:
https://movie.douban.com/j/sea ... rt%3D{}
2、涉及的库:requests、fake_useragent、json、csv
3、软件:PyCharm
【四、项目分析】
1、如何多网页请求?
点击下一页时,每增加一页paged自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
2. 如何获取真正请求的地址?
请求数据时,发现页面上并没有对应数据。其实豆瓣网采用javascript动态加载内容,防止采集。
1)F12右键检查,找到Network,左边菜单Name , 找到第五个数据,点击Preview。
2)点开subjects,可以看到 title 就是对应电影名称。rate就是对应评分。通过js解析subjects字典,找到需要的字段。
3. 如何网页访问?
https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3Bbr />https://movie.douban.com/j/sea ... %3D60
当点击下一页时,每增加一页page自增加20,用{}代替变换的变量,再用for循环遍历这网址,实现多个网址请求。
【五、项目实施】
1、我们定义一个class类继承object,然后定义init方法继承self,再定义一个主函数main继承self。导入需要的库和请求网址。
import requests,jsonfrom fake_useragent import UserAgentimport csv<br />class Doban(object): def __init__(self): self.url = "https://movie.douban.com/j/sea ... rt%3D{}"<br /> def main(self): pass<br />if __name__ == '__main__': Siper = Doban() Siper.main()
2、随机产生UserAgent,构造请求头,防止反爬。
for i in range(1, 50): self.headers = { 'User-Agent': ua.random, }
3、发送请求 ,获取响应,页面回调,方便下次请求。
def get_page(self, url): res = requests.get(url=url, headers=self.headers) html = res.content.decode("utf-8") return html
4、json解析页面数据,获取对应的字典。
data = json.loads(html)['subjects'] # print(data[0])
5、for遍历,获取对应的电影名、 评分、下详情页链接。
print(name, goblin_herf) html2 = self.get_page(goblin_herf) # 第二个发生请求 parse_html2 = etree.HTML(html2) r = parse_html2.xpath('//div[@class="entry"]/p/text()')
6、创建csv文件进行写入,定义对应的标题头内容,保存数据 。
# 创建csv文件进行写入 csv_file = open('scr.csv', 'a', encoding='gbk') csv_writer = csv.writer(csv_file) # 写入csv标题头内容 csv_writerr.writerow(['电影', '评分', "详情页"]) #写入数据 csv_writer.writerow([id, rate, urll])
7、图片地址进行请求。定义图片名称,保存文档。
html2 = requests.get(url=urll, headers=self.headers).content dirname = "./图/" + id + ".jpg" with open(dirname, 'wb') as f: f.write(html2) print("%s 【下载成功!!!!】" % id)
8、调用方法,实现功能。
html = self.get_page(url) self.parse_page(html)
9、项目优化:1)设置时间延时。
time.sleep(1.4)
2)定义一个变量u, for遍历,表示爬取的是第几页。(更清晰可观)。
u = 0 self.u += 1;
【六、效果展示】
1、点击绿色小三角运行输入起始页,终止页( 从0页开始 )。
2、将下载成功信息显示在控制台。
3、保存csv文档。
4、电影图片展示。
【七、总结】
1、不建议抓取太多数据,容易对服务器造成负载,浅尝辄止即可。
2、本文章就Python爬取豆瓣网,在应用中出现的难点和重点,以及如何防止反爬,做出了相对于的解决方案。
3、希望通过这个项目,能够帮助了解json解析页面的基本流程,字符串是如何拼接,format函数如何运用。
4、本文基于Python网络爬虫,利用爬虫库,实现豆瓣电影及其图片的获取。实现的时候,总会有各种各样的问题,切勿眼高手低,勤动手,才可以理解的更加深刻。
5、需要本文源码的小伙伴,请在下方公众号后台回复“豆瓣电影”四个字,即可获取。
看完本文有收获?请转发分享给更多的人
IT共享之家
文章采集调用 好代码和坏代码
采集交流 • 优采云 发表了文章 • 0 个评论 • 127 次浏览 • 2022-05-14 06:28
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
查看全部
文章采集调用 好代码和坏代码
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
V8 中的垃圾收集(GC),图文指南
采集交流 • 优采云 发表了文章 • 0 个评论 • 79 次浏览 • 2022-05-14 06:27
原文标题:Garbage collection in V8, an illustrated guide原文链接:@_lrlna/garbage-collection-in-v8-an-illustrated-guide-d24a952ee3b8
本指南与我迄今为止所写的其他指南都不同,我在里面添加了一些草图。我用草图描绘了垃圾收集(GC)的整个概念以及它是如何在 javascript 中被处理的,更确切地说是在运行 javascript 的引擎中。顺便提一下,这个指南是面向初学者的,不包括 V8 内存管理的各个方面以及 V8 的内部原理。我添加了一些资源,可以帮助你更深入地了解。本指南重点介绍✨javascript✨,对于某些语言而言,垃圾收集是完全不一样的,比如 C 语言.
好的,我们开始吧。
什么是v8?
V8,是一个 JavaScript 的运行时引擎,不要与你最喜爱的番茄汁混淆了,它负责编译并执行你精美的javascript。V8 带有分代垃圾收集器,我将在后文解释。它与 Chrome 一起,而 SpiderMonkey 是 Mozilla 的引擎 Chakra 是微软的。基本上当运行 javascript 时,您需要一个引擎来处理它,而且 V8 是您的选择之一,无论是在浏览器还是在 node.js 环境中。(P.S. V8 是✨ 开源的 ✨。)
什么是垃圾收集?
垃圾收集的重点是通过使用特定的程序来管理内存的使用。诸如 C 之类的语言通常可以直接操作程序中的内存,并在程序的上下文中分配和释放对象。另一方面,ECMAScript 缺少访问内存管理的特定接口(是的,这意味着没有API)。这基本上意味着程序中的所有内存管理权限都被转移到了 V8。
由于我们无法访问无限量的内存,因此垃圾收集器的工作是通过内存中分配的对象来确定它们是否死亡或是活动。那些活着的对象会留在内存中,那些死亡的对象被删除,内存被分配回堆。
什么是堆?堆是非结构化区域,堆中的对象占用分配的内存。这种分配是动态的,因为对象的大小/寿命/数量是未知的,所以需要在运行时分配和释放。
如果我们看一下并发模型,堆直接与调用栈一起工作,因为堆栈中的对象需要进行内存分配。它看起来像这样:
Dead or alive?
如何检查对象的生死,是通过客户机或者程序代码是否可以到达此对象。您可以想到的最容易达到的对象可能是根范围中定义的对象。
一些 C++ 绑定(或客户端上的 Web API)也是根的一部分,因此您可以通过例如 setInterval 直接访问。
可达性(Reachability)还可以这么理解:另一个对象或根是否可以获得它,如果可以的话,该对象所需的内存被保留。
那么我们怎么可以做到垃圾收集呢?(告诉我!告诉我!)
创建新对象或新的“指针”时,V8 会在堆中分配内存。(javascript 没有真正的指针,所以'指针'在技术上只是复制对原始对象的引用)。堆中的不同类型的对象会占用不同的空间,它将被组织成如下:
为了垃圾回收的目的,V8 将堆分为两部分:新生区和老生区。当您执行需要 V8 分配内存的操作时,V8 将在新生区中分配空间。当你继续添加到堆,你最终会耗尽内存,所以 V8 将不得不运行一个 GC 来清理。新创建的对象被分配得很快,并且当对象死亡时被清理(更短和更快的收集)。一旦对象“生存”了一些(确切的说是2个周期)回收扫描周期时,它们被提升到老生区,在一个单独的循环中收集垃圾。
较旧的对象是幸存多于一个垃圾收集扫描的对象,这意味着它们仍然被其他对象引用,并且仍然需要占用该内存。他们通常不引用较年轻的对象,只是引用较旧的对象。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。
sources.js
This guide is crossposted from lrlna’s sketchin guide on github ✨ . 查看全部
V8 中的垃圾收集(GC),图文指南
原文标题:Garbage collection in V8, an illustrated guide原文链接:@_lrlna/garbage-collection-in-v8-an-illustrated-guide-d24a952ee3b8
本指南与我迄今为止所写的其他指南都不同,我在里面添加了一些草图。我用草图描绘了垃圾收集(GC)的整个概念以及它是如何在 javascript 中被处理的,更确切地说是在运行 javascript 的引擎中。顺便提一下,这个指南是面向初学者的,不包括 V8 内存管理的各个方面以及 V8 的内部原理。我添加了一些资源,可以帮助你更深入地了解。本指南重点介绍✨javascript✨,对于某些语言而言,垃圾收集是完全不一样的,比如 C 语言.
好的,我们开始吧。
什么是v8?
V8,是一个 JavaScript 的运行时引擎,不要与你最喜爱的番茄汁混淆了,它负责编译并执行你精美的javascript。V8 带有分代垃圾收集器,我将在后文解释。它与 Chrome 一起,而 SpiderMonkey 是 Mozilla 的引擎 Chakra 是微软的。基本上当运行 javascript 时,您需要一个引擎来处理它,而且 V8 是您的选择之一,无论是在浏览器还是在 node.js 环境中。(P.S. V8 是✨ 开源的 ✨。)
什么是垃圾收集?
垃圾收集的重点是通过使用特定的程序来管理内存的使用。诸如 C 之类的语言通常可以直接操作程序中的内存,并在程序的上下文中分配和释放对象。另一方面,ECMAScript 缺少访问内存管理的特定接口(是的,这意味着没有API)。这基本上意味着程序中的所有内存管理权限都被转移到了 V8。
由于我们无法访问无限量的内存,因此垃圾收集器的工作是通过内存中分配的对象来确定它们是否死亡或是活动。那些活着的对象会留在内存中,那些死亡的对象被删除,内存被分配回堆。
什么是堆?堆是非结构化区域,堆中的对象占用分配的内存。这种分配是动态的,因为对象的大小/寿命/数量是未知的,所以需要在运行时分配和释放。
如果我们看一下并发模型,堆直接与调用栈一起工作,因为堆栈中的对象需要进行内存分配。它看起来像这样:
Dead or alive?
如何检查对象的生死,是通过客户机或者程序代码是否可以到达此对象。您可以想到的最容易达到的对象可能是根范围中定义的对象。
一些 C++ 绑定(或客户端上的 Web API)也是根的一部分,因此您可以通过例如 setInterval 直接访问。
可达性(Reachability)还可以这么理解:另一个对象或根是否可以获得它,如果可以的话,该对象所需的内存被保留。
那么我们怎么可以做到垃圾收集呢?(告诉我!告诉我!)
创建新对象或新的“指针”时,V8 会在堆中分配内存。(javascript 没有真正的指针,所以'指针'在技术上只是复制对原始对象的引用)。堆中的不同类型的对象会占用不同的空间,它将被组织成如下:
为了垃圾回收的目的,V8 将堆分为两部分:新生区和老生区。当您执行需要 V8 分配内存的操作时,V8 将在新生区中分配空间。当你继续添加到堆,你最终会耗尽内存,所以 V8 将不得不运行一个 GC 来清理。新创建的对象被分配得很快,并且当对象死亡时被清理(更短和更快的收集)。一旦对象“生存”了一些(确切的说是2个周期)回收扫描周期时,它们被提升到老生区,在一个单独的循环中收集垃圾。
较旧的对象是幸存多于一个垃圾收集扫描的对象,这意味着它们仍然被其他对象引用,并且仍然需要占用该内存。他们通常不引用较年轻的对象,只是引用较旧的对象。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。
sources.js
This guide is crossposted from lrlna’s sketchin guide on github ✨ .
文章采集调用 好代码和坏代码
采集交流 • 优采云 发表了文章 • 0 个评论 • 139 次浏览 • 2022-05-10 05:21
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
查看全部
文章采集调用 好代码和坏代码
关注后回复“进群”,拉你进程序员交流群
要写出好代码,首先需要提升品位。
很多软件工程师写不好代码,在评审他人的代码时也看不出问题,就是因为缺乏对好代码标准的认识。
现在还有太多的软件工程师认为,代码只要可以正确执行就可以了。这是一种非常低的评价标准,很多重要的方面都被忽视了。
好代码的特性
好代码具有以下特性。
1. 鲁棒(Solid and Robust)
代码不仅要被正确执行,我们还要考虑对各种错误情况的处理,比如各种系统调用和函数调用的异常情况,系统相关组件的异常和错误。
对很多产品级的程序来说,异常和错误处理的逻辑占了很大比例。
2. 高效(Fast)
程序的运行应使用尽量少的资源。资源不仅仅包括CPU,还可能包括存储、I/O等。
设计高效的程序,会运用到数据结构和算法方面的知识,同时要考虑到程序运行时的各种约束条件。
3. 简洁(Maintainable and Simple)
代码的逻辑要尽量简明易懂,代码要具有很好的可维护性。对于同样的目标,能够使用简单清楚的方法达成,就不要使用复杂晦涩的方法。
“大道至简”,能否把复杂的问题用简单的方式实现出来,这是一种编程水平的体现。
4. 简短(Small)
在某种意义上,代码的复杂度和维护成本是和代码的规模直接相关的。在实现同样功能的时候,要尽量将代码写得简短一些。
简洁高于简短。这里要注意,某些人为了能把代码写得简短,使用了一些晦涩难懂的描述方式,降低了代码的可读性。这种方式是不可取的。
5. 可测试(Testable)
代码的正确性要通过测试来保证,尤其是在敏捷的场景下,更需要依赖可自动回归执行的测试用例。
在代码的设计中,要考虑如何使代码可测、易测。一个比较好的实践是使用TDD(Test-Driven Development,测试驱动开发)的方法,这样在编写测试用例的时候会很快发现代码在可测试性方面的问题。
6. 共享(Re-Usable)
大量的程序实际上都使用了类似的框架或逻辑。由于目前开源代码的大量普及,很多功能并不需要重复开发,只进行引用和使用即可。
在一个组织内部,应鼓励共享和重用代码,这样可以有效降低代码研发的成本,并提升代码的质量。
实现代码的共享,不仅需要在意识方面提升,还需要具有相关的能力(如编写独立、高质量的代码库)及相关基础设施的支持(如代码搜索、代码引用机制)。
7. 可移植(Portable)
某些程序需要在多种操作系统下运行,在这种情况下,代码的可移植性成为一种必需的能力。
要让代码具有可移植性,需要对所运行的各种操作系统底层有充分的理解和统一抽象。一般会使用一个适配层来屏蔽操作系统底层的差异。
一些编程语言也提供了多操作系统的可移植性,如很多基于Python语言、Java语言、Go语言编写的程序,都可以跨平台运行。
8. 可观测(Observable)/ 可监控(Monitorable)
面对目前大量存在的在线服务(Online Service)程序,需要具备对程序的运行状态进行细致而持续监控的能力。
这要求在程序设计时就提供相关的机制,包括程序状态的收集、保存和对外输出。
9. 可运维(Operational)
可运维已经成为软件研发活动的重要组成部分,可运维重点关注成本、效率和稳定性三个方面。
程序的可运维性和程序的设计、编写紧密相关,如果在程序设计阶段就没有考虑可运维性,那么程序运行的运维目标则难以达成。
10. 可扩展(Scalable andExtensible)
可扩展包含“容量可扩展”(Scalable)和“功能可扩展”(Extensible)两方面。
在互联网公司的系统设计中,“容量可扩展”是重要的设计目标之一。系统要尽量支持通过增加资源来实现容量的线性提高。
快速响应需求的变化,是互联网公司的另外一个重要挑战。可考虑使用插件式的程序设计方式,以容纳未来可能新增的功能,也可考虑使用类似Protocol Buffer 这样的工具,支持对协议新增字段。
以上十条标准,如果要记住,可能有些困难。我们可以把它们归纳为四个方面,见表1。
表1对一流代码特性的汇总分类
坏代码的例子
关于好代码,上面介绍了一些特性,本节也给出坏代码(Bad Code)的几个例子。关于坏代码,本书没有做系统性总结,只是希望通过以下这些例子的展示让读者对坏代码有直观的感觉。
1.不好的函数名称(Bad Function Name)
如do(),这样的函数名称没有多少信息量;又如myFunc(),这样的函数名称,个人色彩过于强烈,也没有足够的信息量。
2.不好的变量名称(Bad Variable Name)
如a、b、c、i、j、k、temp,这样的变量名称在很多教科书中经常出现,很多人在上学期间写代码时也会经常这样用。如果作为局部变量,这样的名称有时是可以接受的;但如果作为作用域稍微大的变量,这样的名称就非常不可取了。
3.没有注释(No Comments)
有写注释习惯的软件工程师很少,很多软件工程师认为写注释是浪费时间,是“额外”的工作。但是没有注释的代码,阅读的成本会比较高。
4.函数不是单一目的(The Function has No Single Purpose)
如LoadFromFileAndCalculate()。这个例子是我编造的,但现实中这样的函数其实不少。很多函数在首次写出来的时候,就很难表述清楚其用途;还有一些函数随着功能的扩展,变得越来越庞杂,也就慢慢地说不清它的目的了。
这方面的问题可能很多人都没有充分地认识到——非单一目的的函数难以维护,也难以复用。
5.不好的排版(Bad Layout)
不少人认为,程序可以正常执行就行了,所以一些软件工程师不重视对代码的排版,认为这仅仅是一种“形式”。
没有排好版的程序,在阅读效率方面会带来严重问题。这里举一个极端的例子:对于C语言来说,“;”可作为语句的分割符,而“缩进”和“换行”对于编译器来说是无用的,所以完全可以把一段C语言程序都“压缩”在一行内。这样的程序是可以运行的,但是对人来说,可读性非常差。这样的程序肯定是我们非常不希望看到的。
6.无法测试(None Testable)
程序的正确性要依赖测试来保证(虽然测试并不能保证程序完全无错)。无法或不好为之编写测试用例的程序,是很难有质量保证的。
好代码从哪里来
上一节说明了好代码的特性,本节来分析好代码是如何产出的。
▊ 好代码不止于编码
好代码从哪里来?
对于这个问题,很多读者肯定会说:“好代码肯定是写出来的呀。”
我曾做过多次调研,发现很多软件工程师日常所读的书确实是和“写代码”紧密相关的。
但是,这里要告诉读者的是,代码不只是“写”出来的。在很多年前,我所读的软件工程方面的教科书就告诉我,编码的时间一般只占一个项目所花时间的 10%。我曾说过一句比较有趣的话:
“如果一个从业者告诉你,他的大部分时间都在写代码,那么他大概率不是一个高级软件工程师。”
那么,软件工程师的时间都花到哪里去了呢?软件工程师的时间应该花在哪里呢?
好的代码是多个工作环节的综合结果。
(1)在编码前,需要做好需求分析和系统设计。而这两项工作是经常被大量软件工程师忽略或轻视的环节。
(2)在编码时,需要编写代码和编写单元测试。对于“编写代码”,读者都了解;而对于“编写单元测试”,有些软件工程师就不认同了,甚至还有人误以为单元测试是由测试工程师来编写的。
(3)在编码后,要做集成测试、上线,以及持续运营/迭代改进。这几件事情都是要花费不少精力的,比如上线,不仅仅要做程序部署,而且要考虑程序是如何被监控的。有时,为了一段程序的上线,设计和实施监控的方案要花费好几天才能完成。
因此,一个好的系统或产品是以上这些环节持续循环执行的结果。
▊ 需求分析和系统设计
1.几种常见的错误现象
相对于编码工作,需求分析和系统设计是两个经常被忽视的环节。在现实工作中,我们经常会看到以下这些现象。
(1)很多人错误地认为,写代码才是最重要的事情。不少软件工程师如果一天没有写出几行代码,就会认为工作没有进展;很多管理者也会以代码的产出量作为衡量工作结果的主要标准,催促软件工程师尽早开始写代码。
(2)有太多的从业者,在没有搞清楚项目目标之前就已经开始编码了。在很多时候,项目目标都是通过并不准确的口头沟通来确定的。例如:
“需要做什么?”
“就按照×××网站的做一个吧。”
(3)有太多的从业者,在代码编写基本完成后,才发现设计思路是有问题的。他们在很多项目上花费很少(甚至没有花费)时间进行系统设计,对于在设计中所隐藏的问题并没有仔细思考和求证。基于这样的设计投入和设计质量,项目出现设计失误也是很难避免的。而面对一个已经完成了基本编码的项目,如果要“动大手术”来修改它,相信每个有过类似经历的人都一定深知那种感受——越改越乱,越改越着急。
以上这几种情况,很多读者是不是都有过类似经历?
2.研发前期多投入,收益更大
关于软件研发,首先我们需要建立一个非常重要的观念。
在研发前期(需求分析和系统设计)多投入资源,相对于把资源都投入在研发后期(编码、测试等),其收益更大。
这是为什么呢?
要回答这个问题,需要从软件研发全生命周期的角度来考量软件研发的成本。除编码外,软件测试、上线、调试等都需要很高成本。如果我们把需求搞错了,那么与错误需求有关的设计、编码、测试、上线等成本就都浪费了;如果我们把设计搞错了,那么与错误设计相关的编码、测试、上线的成本也就浪费了。
如果仔细考量那些低效的项目,会发现有非常多的类似于上面提到的“浪费”的地方。软件工程师似乎都很忙,但是在错误方向上所做的所有努力并不会产生任何价值,而大部分的加班实际上是在做错误的事情,或者是为了补救错误而努力。在这种情况下,将更多的资源和注意力向研发前期倾斜会立刻收到良好的效果。
3.修改代码和修改文档,哪个成本更高
很多软件工程师不愿意做需求分析和系统设计,是因为对“写文档”有着根深蒂固的偏见。这里问大家一个问题,如果大家对这个问题能给出正确的回答,那么在“写文档”的意识方面,一定会有很大的转变。
任何人都不是神仙,无法一次就把所有事情做对。对于一段程序来说,它一定要经过一定周期的修改和迭代。这时有两种选择:
选择一:修改文档。在设计文档时完成迭代调整,待没有大问题后再开始编码。
选择二:修改代码。只有粗略的设计文档,或者没有设计文档,直接开始编码,所有的迭代调整都在代码上完成。
请大家判断,修改代码和修改文档,哪个成本更高?
在之前的一些分享交流会上,对于这个问题,有人会说,修改文档的成本更高。因为在修改文档后还要修改代码,多了一道手续。而直接修改代码,只需要做一次,这样更直接。
这个回答说明了回答者没有充分理解“先写文档,后写代码”的设计方法。如果没有充分重视设计文档的工作,在输出的设计文档质量不高的情况下就开始编码,确实会出现以上提到的问题。但是,如果在设计文档阶段就已经做了充分考虑,会减少对代码的迭代和反复。
对于同样的设计修改,“修改代码”的成本远高于“修改文档”。这是因为,在设计文档中只会涉及主要的逻辑,那些细小的、显而易见的逻辑不会在设计文档中出现。在修改设计文档时,也只会影响到这些主要逻辑。而如果在代码中做修改,不仅会涉及这些主要逻辑,而且会涉及那些在文档中不会出现的细小逻辑。对于一段程序来说,任何一个逻辑出现问题,程序都是无法正常运行的。
4.需求分析和系统设计之间的差别
很多读者无法清楚地区分“需求分析”和“系统设计”之间的差别,于是会发现,在写出的文档中,有些需求分析文档里出现了系统设计的内容,而有些系统设计文档里又混杂了需求分析的内容。
我们用几句话可以非常明确地给出二者的差异。
(1)需求分析:定义系统/软件的黑盒的行为,它是从外部(External)看到的,在说明“是什么”(What)。
(2)系统设计:设计系统/软件的白盒的机制,它是从内部(Internal)看到的,要说明“怎么做”(How)和“为什么”(Why)。
比如,对一辆汽车来说,首先使用者从外部可以看到车厢、车轮,坐在车里可以看到方向盘、刹车踏板、油门踏板等;操作方向盘可以改变汽车的行驶方向,脚踩刹车踏板、油门踏板可用于减速和加速。以上这些是对汽车的“需求分析”。
然后,我们想象汽车外壳和内部变成了透明的,可以看到汽车内部的发动机、变速箱、传动杆、与刹车相关的内部装置等。而这些对驾驶者来说是不可见的,它们是对汽车的“系统设计”。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
Spring Boot 如何监控 SQL 运行情况?
采集交流 • 优采云 发表了文章 • 0 个评论 • 88 次浏览 • 2022-05-09 12:17
关注后回复“进群”,拉你进程序员交流群
今天想和大家聊一聊 Druid 中的监控功能。
Druid 数据库连接池相信很多小伙伴都用过,个人感觉 Druid 是阿里比较成功的开源项目了,不像 Fastjson 那么多槽点,Druid 各方面一直都比较出色,功能齐全,使用也方便,基本的用法就不说了,今天我们来看看 Druid 中的监控功能。
1. 准备工作
首先我们来创建一个 Spring Boot 工程,引入 MyBatis 等,如下:
选一下 MyBatis 和 MySQL 驱动,做一个简单的测试案例。
先来连接一下数据库:
spring.datasource.username=root<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.url=jdbc:mysql:///test05?serverTimezone=Asia/Shanghai<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
创建一个 User 实体类,做一个简单的查询案例,如下:
public class User {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private Integer id;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String username;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String address;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String password;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String email;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> //省略 getter/setter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Mapper<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public interface UserMapper {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> List getUserByUsername(String username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Service<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserService {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserMapper userMapper;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUserByUsername(String username){<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userMapper.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@RestController<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserController {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserService userService;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @GetMapping("/user")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUser(String username) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userService.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
UserMapper.xml 如下:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> select * from user where username=#{username}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
一个很简单的测试,没啥好说的。
这个环境搭建大家随意,如果你已经有持久化的案例了,那就直接看第二小节引入 Druid。
现在这个工程默认的使用的数据库连接池是 HikariDataSource,这是 Spring Boot 中默认的一个数据库连接池,其实这个也还不错。
2. 引入 Druid
接下来我们引入 Druid:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> com.alibaba<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid-spring-boot-starter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 1.2.8<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
注意,Spring Boot 引入的 Druid 是上面这个,这个将来配置监控的时候方便一些。
接下来我们在 application.properties 中配置 WebStatFilter,WebStatFilter 用于采集 web-jdbc 关联监控的数据:
# 启用 WebStatFilter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置拦截规则<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.url-pattern=/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 排除一些不必要的 url,这些 URL 不会涉及到 SQL 查询<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 session 统计功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 缺省 sessionStatMaxCount 是 1000 个,我们可以按需要进行配置<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-max-count=1000<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置 principalSessionName,使得 druid 能够知道当前的 session 的用户是谁<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 根据需要,这个参数的值是 user 信息保存在 session 中的 sessionName<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-session-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 下面这个配置的作用和上面配置的作用类似,这个是通过 Cookie 来识别用户<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-cookie-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 profile 后就能够监控单个 URL 地址调用列表<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.profile-enable=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
我们配置前面五个就可以了,后面三个可以不用配置,各项配置的含义松哥已经在代码中列出来了。
接下来开启 StatViewServlet 的配置,如下:
# 启用内置的监控页面<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 内置监控页面的地址<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 Reset All 功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.reset-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录用户名<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-username=javaboy<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录密码<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 白名单(如果allow没有配置或者为空,则允许所有访问)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.allow=127.0.0.1<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 黑名单(deny 优先于 allow,如果在 deny 列表中,就算在 allow 列表中,也会被拒绝)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.deny=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
配置一下页面地址,配置一下黑白名单即可。
需要注意的是,reset-enable 属性即使设置为 false,重置按钮也会显示,只是点击该按钮并不会重置而已。
好啦,这就完事了。
3. 测试
好啦,接下来我们启动 Spring Boot 项目进行测试。
Spring Boot 项目启动成功后,首先访问如下地址:
此时我们会看到登录认证页面,如下:
输入我们前面配置的用户名/密码(javaboy/123)进行登录,登录成功后,可以看到如下页面:
从标题栏就可以看到,数据源、SQL 监控、SQL 防火墙等功能都是一应俱全。
接下来我们访问:8080/user?username=aaa地址,执行一条 SQL,执行完成后,我们来查看 SQL 监控:
可以看到,此时就有 SQL 执行的监控记录了。
其他的监控数据也都可以看到,我就不一一列举了。如果小伙伴们觉得这里展示的数据不直观,想自己画 HTML 页面,那也是可以的,点击最后面的 JSON API,可以看到每一个监控项的 JSON 地址,拿着 JSON 自己想怎么显示就怎么显示。
4. 去广告
如果想直接用这个监控页面,这个上面有阿里的广告,如下图,公司用的话就特别别扭:
我们可能想去掉这个广告,这也很容易。
首先,经过分析,我们发现广告是由一个叫做 common.js 的文件构建出来的,该文件位于druid-1.2.8.jar!/support/http/resources/js/common.js这里,common.js 文件中有如下几行:
init : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> this.buildFooter();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid.lang.init();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />buildFooter : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> var html ='';<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> $(document.body).append(html);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
大概逻辑就是上面这样,buildFooter 方法负责构建页面末尾的广告,在 init 方法中完成对 buildFooter 方法的调用。
那么想要去除广告,就别调用 buildFooter 方法就行了。
所以我们的去广告思路也很简单,写一个过滤器,拦截下对 common.js 的请求,然后做一点点修改,如下:
@WebFilter(urlPatterns = "/druid/js/common.js")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class RemoveAdFilter implements Filter {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Override<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> String text = Utils.readFromResource("support/http/resources/js/common.js");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> text = text.replace("this.buildFooter();", "");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> servletResponse.getWriter().write(text);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
可以看到,这个过滤器就是拦截/druid/js/common.js请求,拦截到之后,自己去文件中读取 common.js 文件,然后手动替换掉this.buildFooter();这一句就行了,最后再把文件写出去就行了。
当然,记得在启动类中扫描 Filter,如下:
@SpringBootApplication<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@ServletComponentScan("org.javaboy.druid_monitor.filter")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class DruidMonitorApplication {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public static void main(String[] args) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> SpringApplication.run(DruidMonitorApplication.class, args);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
好啦,这就可以啦,有了这个过滤器,广告就没了。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
查看全部
Spring Boot 如何监控 SQL 运行情况?
关注后回复“进群”,拉你进程序员交流群
今天想和大家聊一聊 Druid 中的监控功能。
Druid 数据库连接池相信很多小伙伴都用过,个人感觉 Druid 是阿里比较成功的开源项目了,不像 Fastjson 那么多槽点,Druid 各方面一直都比较出色,功能齐全,使用也方便,基本的用法就不说了,今天我们来看看 Druid 中的监控功能。
1. 准备工作
首先我们来创建一个 Spring Boot 工程,引入 MyBatis 等,如下:
选一下 MyBatis 和 MySQL 驱动,做一个简单的测试案例。
先来连接一下数据库:
spring.datasource.username=root<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.url=jdbc:mysql:///test05?serverTimezone=Asia/Shanghai<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
创建一个 User 实体类,做一个简单的查询案例,如下:
public class User {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private Integer id;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String username;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String address;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String password;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> private String email;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> //省略 getter/setter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Mapper<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public interface UserMapper {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> List getUserByUsername(String username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@Service<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserService {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserMapper userMapper;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUserByUsername(String username){<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userMapper.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@RestController<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class UserController {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Autowired<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> UserService userService;<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @GetMapping("/user")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public List getUser(String username) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> return userService.getUserByUsername(username);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
UserMapper.xml 如下:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> select * from user where username=#{username}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
一个很简单的测试,没啥好说的。
这个环境搭建大家随意,如果你已经有持久化的案例了,那就直接看第二小节引入 Druid。
现在这个工程默认的使用的数据库连接池是 HikariDataSource,这是 Spring Boot 中默认的一个数据库连接池,其实这个也还不错。
2. 引入 Druid
接下来我们引入 Druid:
<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> com.alibaba<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid-spring-boot-starter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> 1.2.8<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
注意,Spring Boot 引入的 Druid 是上面这个,这个将来配置监控的时候方便一些。
接下来我们在 application.properties 中配置 WebStatFilter,WebStatFilter 用于采集 web-jdbc 关联监控的数据:
# 启用 WebStatFilter<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置拦截规则<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.url-pattern=/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 排除一些不必要的 url,这些 URL 不会涉及到 SQL 查询<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 session 统计功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 缺省 sessionStatMaxCount 是 1000 个,我们可以按需要进行配置<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.web-stat-filter.session-stat-max-count=1000<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 配置 principalSessionName,使得 druid 能够知道当前的 session 的用户是谁<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 根据需要,这个参数的值是 user 信息保存在 session 中的 sessionName<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-session-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 下面这个配置的作用和上面配置的作用类似,这个是通过 Cookie 来识别用户<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.principal-cookie-name=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 profile 后就能够监控单个 URL 地址调用列表<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />#spring.datasource.druid.web-stat-filter.profile-enable=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
我们配置前面五个就可以了,后面三个可以不用配置,各项配置的含义松哥已经在代码中列出来了。
接下来开启 StatViewServlet 的配置,如下:
# 启用内置的监控页面<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.enabled=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 内置监控页面的地址<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 开启 Reset All 功能<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.reset-enable=true<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录用户名<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-username=javaboy<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 设置登录密码<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.login-password=123<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 白名单(如果allow没有配置或者为空,则允许所有访问)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.allow=127.0.0.1<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /># 黑名单(deny 优先于 allow,如果在 deny 列表中,就算在 allow 列表中,也会被拒绝)<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />spring.datasource.druid.stat-view-servlet.deny=<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
配置一下页面地址,配置一下黑白名单即可。
需要注意的是,reset-enable 属性即使设置为 false,重置按钮也会显示,只是点击该按钮并不会重置而已。
好啦,这就完事了。
3. 测试
好啦,接下来我们启动 Spring Boot 项目进行测试。
Spring Boot 项目启动成功后,首先访问如下地址:
此时我们会看到登录认证页面,如下:
输入我们前面配置的用户名/密码(javaboy/123)进行登录,登录成功后,可以看到如下页面:
从标题栏就可以看到,数据源、SQL 监控、SQL 防火墙等功能都是一应俱全。
接下来我们访问:8080/user?username=aaa地址,执行一条 SQL,执行完成后,我们来查看 SQL 监控:
可以看到,此时就有 SQL 执行的监控记录了。
其他的监控数据也都可以看到,我就不一一列举了。如果小伙伴们觉得这里展示的数据不直观,想自己画 HTML 页面,那也是可以的,点击最后面的 JSON API,可以看到每一个监控项的 JSON 地址,拿着 JSON 自己想怎么显示就怎么显示。
4. 去广告
如果想直接用这个监控页面,这个上面有阿里的广告,如下图,公司用的话就特别别扭:
我们可能想去掉这个广告,这也很容易。
首先,经过分析,我们发现广告是由一个叫做 common.js 的文件构建出来的,该文件位于druid-1.2.8.jar!/support/http/resources/js/common.js这里,common.js 文件中有如下几行:
init : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> this.buildFooter();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> druid.lang.init();<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />buildFooter : function() {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> var html ='';<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> $(document.body).append(html);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />},<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
大概逻辑就是上面这样,buildFooter 方法负责构建页面末尾的广告,在 init 方法中完成对 buildFooter 方法的调用。
那么想要去除广告,就别调用 buildFooter 方法就行了。
所以我们的去广告思路也很简单,写一个过滤器,拦截下对 common.js 的请求,然后做一点点修改,如下:
@WebFilter(urlPatterns = "/druid/js/common.js")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class RemoveAdFilter implements Filter {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> @Override<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> String text = Utils.readFromResource("support/http/resources/js/common.js");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> text = text.replace("this.buildFooter();", "");<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> servletResponse.getWriter().write(text);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
可以看到,这个过滤器就是拦截/druid/js/common.js请求,拦截到之后,自己去文件中读取 common.js 文件,然后手动替换掉this.buildFooter();这一句就行了,最后再把文件写出去就行了。
当然,记得在启动类中扫描 Filter,如下:
@SpringBootApplication<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />@ServletComponentScan("org.javaboy.druid_monitor.filter")<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />public class DruidMonitorApplication {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> public static void main(String[] args) {<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> SpringApplication.run(DruidMonitorApplication.class, args);<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /> }<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" /><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />}<br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;" />
好啦,这就可以啦,有了这个过滤器,广告就没了。
-End-
最近有一些小伙伴,让我帮忙找一些面试题资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
点击卡片,关注后回复【面试题】即可获取
在看点这里
一文详解JVM垃圾收集机制,动图帮你轻松理解大厂面试难点
采集交流 • 优采云 发表了文章 • 0 个评论 • 89 次浏览 • 2022-05-09 12:05
前言
上篇文章已经给大家介绍了 JVM 的架构和运行时数据区 (内存区域),本篇文章将给大家介绍 JVM 的重点内容——垃圾收集。众所周知,相比 C / C++ 等语言,Java 可以省去手动管理内存的繁琐操作,很大程度上解放了 Java 程序员的生产力,而这正是得益于 JVM 的垃圾收集机制和内存分配策略。我们平时写程序时并感知不到这一点,但是如果是在生产环境中,JVM 的不同配置对于服务器性能的影响是非常大的,所以掌握 JVM 调优是高级 Java 工程师的必备技能。正所谓“基础不牢,地动山摇”,在这之前我们先来了解一下底层的 JVM 垃圾收集机制。
既然要介绍垃圾收集机制,就要搞清楚以下几个问题:
哪些内存区域需要进行垃圾收集?
如何判断对象是否可回收?
新的对象是如何进行内存分配的?
如何进行垃圾收集?
本文将按以下行文结构展开,对上述问题一一解答。
需要进行垃圾收集的内存区域;
判断对象是否可回收的方法;
主流的垃圾收集算法介绍;
JVM 的内存分配与垃圾收集机制。
下面开始正文,还是图文并茂的老配方,走起。
一、需要进行垃圾收集的内存区域
先来回顾一下 JVM 的运行时数据区:
JVM 运行时数据区
其中程序计数器、Java 虚拟机栈和本地方法栈都是线程私有的,与其对应的线程是共生关系,随线程而生,随线程而灭,栈中的栈帧也随着方法的进入和退出井然有序地进行入栈和出栈操作。所以这几个区域的内存分配和回收都是有很大确定性的,在方法结束或线程结束时,内存也会随之释放,因此也就不需要考虑这几个区域的内存回收问题了。
而堆和方法区就不一样了,Java 的对象几乎都是在堆上创建出来的,方法区则存储了被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,方法区中的运行时常量池则存放了各种字面量与符号引用,上述的这些数据大部分都是在运行时才能确定的,所以需要进行动态的内存管理。
还要说明一点,JVM 中的垃圾收集器的最主要的关注对象是 Java 堆,因为这里进行垃圾收集的“性价比”是最高的,尤其是在新生代 (后文对分代算法进行介绍) 中的垃圾收集,一次就可以回收 70% - 99% 的内存。而方法区由于垃圾收集判定条件,尤其是类型卸载的判定条件相当苛刻,其回收性价比是非常低的,因此有些垃圾收集器就干脆不支持或不完全支持方法区的垃圾收集,比如 JDK 11 中的 ZGC 收集器就不支持类型卸载。
二、判断对象是否可回收的方法2.1 引用计数法
引用计数法的实现很简单,在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。大部分情况下这个方法是可以发挥作用的,但是在存在循环引用的情况下,引用计数法就无能为力了。比如下面这种情况:
public class Student {<br /> // friend 字段<br /> public Student friend = null;<br /> <br /> public static void test() {<br /> Student a = new Student();<br /> Student b = new Student();<br /> a.friend = b;<br /> b.friend = a;<br /> a = null;<br /> b = null;<br /> System.gc();<br /> }<br />}
上述代码创建了 a 和 b 两个 Student 实例,并把它们各自的 friend 字段赋值为对方,除此之外,这两个对象再无任何引用,然后将它们都赋值为 null,在这种情况下,这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也就无法回收它们。如下图所示:
循环引用
但是在 Java 程序中,a 和 b 是可以被回收的,因为 JVM 并没有使用引用计数法判定对象是否可回收,而是采用了可达性分析法。
2.2 可达性分析法
这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集 (GC Root Set),从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链” (Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,则说明此对象不再被使用,也就可以被回收了。要进行可达性分析就需要先枚举根节点 (GC Roots),在枚举根节点过程中,为防止对象的引用关系发生变化,需要暂停所有用户线程 (垃圾收集之外的线程),这种暂停全部用户线程的行为被称为 (Stop The World)。可达性分析法如下图所示:
可达性分析法
图中绿色的都是位于 GC Root Set 中的 GC Roots,所有与其有关联的对象都是可达的,被标记为蓝色,而所有与其没有任何关联的对象都是不可达的,被标记为灰色。即使是不可达对象,也并非一定会被回收,如果该对象同时满足以下几个条件,那么它仍有“逃生”的可能:
该对象有重写的finalize()方法 (Object 类中的方法);
finalize()方法中将其自身链接到了引用链上;
JVM 此前没有调用过该对象的finalize()方法 (因为 JVM 在收集可回收对象时会调用且仅调用一次该对象的finalize()方法)。
不过由于finalize()方法的运行代价高昂,不确定性大,且无法保证各个对象的调用顺序,所以并不推荐使用。那么 GC Roots 又是何方神圣呢?在 Java 语言中,固定可作为GC Roots的对象包括以下几种:
在虚拟机栈 (栈帧中的本地变量表) 中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。
在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用。
在本地方法栈中JNI (即通常所说的Native方法) 引用的对象。
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常见的异常对象 (比如
NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器。
所有被同步锁 (synchronized关键字) 持有的对象。
反映Java虚拟机内部情况的 JM XBean、JVM TI 中注册的回调、本地代码缓存等。
三、垃圾收集算法介绍3.1 标记-清除算法
标记-清除算法的思想很简单,顾名思义,该算法的过程分为标记和清除两个阶段:首先标记出所有需要回收的对象,其中标记过程就是使用可达性分析法判断对象是否属于垃圾的过程。在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。示意图如下:
标记清除算法
这个算法虽然很简单,但是有两个明显的缺点:
执行效率不稳定。如果 Java 堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
导致内存空间碎片化。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作,非常影响程序运行效率。
3.2 标记-复制算法
标记-复制算法常简称复制算法,这一算法正好解决了标记-清除算法在面对大量可回收对象时执行效率低下的问题。其实现方法也很易懂:在可用内存中划分出两块大小相同的区域,每次只使用其中一块,另一块保持空闲状态,第一块用完的时候,就把存活的对象全部复制到第二块区域,然后把第一块全部清空。如下图所示:
标记-复制算法
这个算法很适合用于对象存活率低的情况,因为它只关注存活对象而无需理会可回收对象,所以 JVM 中新生代的垃圾收集正是采用的这一算法。但是其缺点也很明显,每次都要浪费一半的内存,未免太过奢侈,不过 JVM 中的新生代有更精细的内存划分,比较好地解决了这个问题,见下文。
3.3 标记-整理算法
这个算法完美解决了标记-清除算法的空间碎片化问题,其标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记整理算法
这个算法虽然可以很好地解决空间碎片化问题,但是每次垃圾回收都要移动存活的对象,还要对引用这些对象的地方进行更新,对象移动的操作也需要全程暂停用户线程 (Stop The World)。
3.4 分代收集算法
与其说是算法,不如说是理论。如今大多数虚拟机的实现版本都遵循了“分代收集”的理论进行设计,这个理论可以看作是经验之谈,因为开发人员在开发过程中发现了 JVM 中存活对象的数量和它们的年龄之间有着某种规律,如下图:
JVM 中存活对象数量与年龄之间的关系
在此基础上,人们提出了以下假说:
绝大多数对象都是朝生夕灭的。
熬过越多次垃圾收集过程的对象就越难以消亡。
根据这两个假说,可以把 JVM 的堆内存大致分为新生代和老年代,新生代对象大多存活时间短,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间,所以这一区域一般采用标记-复制算法进行垃圾收集,频率比较高。而老年代则是一些难以消亡的对象,可以采用标记-清除和标记整理算法进行垃圾收集,频率可以低一些。
按照 Hotspot 虚拟机的实现,针对新生代和老年代的垃圾收集又分为不同的类型,也有不同的名词,如下:
部分收集 (Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:新生代收集 (Minor GC / Young GC):指目标只是新生代的垃圾收集。老年代收集 (Major GC / Old GC):指目标只是老年代的垃圾收集,目前只有CMS收集器的并发收集阶段是单独收集老年代的行为。混合收集 (Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
整堆收集 (Full GC):收集整个Java堆和方法区的垃圾收集。
人们经常会混淆 Major GC 和 Full GC,不过这也有情可原,因为这两种 GC 行为都包含了老年代的垃圾收集,而单独的老年代收集 (Major GC) 又比较少见,大多数情况下只要包含老年代收集,就会是整堆收集 (Full GC),不过还是分得清楚一点比较好哈。
四、JVM 的内存分配和垃圾收集机制
经过前面的铺垫,现在终于可以一窥 JVM 的内存分配和垃圾收集机制的真面目了。
4.1 JVM 堆内存的划分
JVM 堆内存划分,从Java 8开始不再有永久代
Java 堆是 JVM 所管理的内存中最大的一块,也是垃圾收集器的管理区域。大多数垃圾收集器都会将堆内存划分为上图所示的几个区域,整体分为新生代和老年代,比例为 1 : 2,新生代又进一步分为 Eden、From Survivor 和 To Survivor,默认比例为 8 : 1 : 1,请注意,可通过 SurvivorRatio 参数进行设置。请注意,从 JDK 8 开始,JVM 中已经不再有永久代的概念了。Java 堆上的无论哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
4.2 分代收集原理4.2.1 新生代中对象的分配与回收
大多数情况下,对象优先在新生代 Eden 区中分配,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。Eden、From Survivor 和 To Survivor 的比例为 8 : 1 : 1,之所以按这个比例是因为绝大多数对象都是朝生夕灭的,垃圾收集时 Eden 存活的对象数量不会太多,Survivor 空间小一点也足以容纳,每次新生代中可用内存空间为整个新生代容量的90% (Eden 的 80% 加上 To Survivor 的 10%),只有From Survivor 空间,即 10% 的新生代是会被“浪费”的。不会像原始的标记-复制算法那样浪费一半的内存空间。From Survivor 和 To Survivor 的空间并不是固定的,而是在 S0 和 S1 之间动态转换的,第一次 Minor GC 时会选择 S1 作为 To Survivor,并将 Eden 中存活的对象复制到其中,并将对象的年龄加1,注意新生代使用的垃圾收集算法是标记-复制算法的改良版。下面是示意图,请注意其中第一步的变色是为了醒目,虚拟机只做了标记存活对象的操作。
第一次 Minor GC 示意图
在后续的 Minor GC 中,S0 和 S1会交替转化为 From Survivor 和 To Survivor,Eden 和 From Survivor 中的存活对象会复制到 To Survivor 中,并将年龄加大 1。如下图所示:
后续 Minor GC 示意图
4.2.2 对象晋升老年代
在以下这些情况下,对象会晋升到老年代。
长期存活对象将进入老年代
对象在 Survivor 区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 设置,这个参数的最大值是15,因为对象年龄信息储存在对象头中,占4个比特 (bit)的内存,所能表示最大数字就是15。
长期存活对象晋升老年代示意图
2.大对象可以直接进入老年代
对于大对象,尤其是很长的字符串,或者元素数量很多的数组,如果分配在 Eden 中,会很容易过早占满 Eden 空间导致 Minor GC,而且大对象在 Eden 和两个 Survivor 之间的来回复制也还会有很大的内存复制开销。所以我们可以通过设置 -XX:PretenureSizeThreshold 的虚拟机参数让大对象直接进入老年代。
3.动态对象年龄判断
为了能更好地适应不同程序的内存状况,HotSpot 虚拟机并不是永远要求对象的年龄必须达到 -XX:MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 -XX:MaxTenuringThreshold 中要求的年龄。
4.空间分配担保 (Handle Promotion)
当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域 (实际上大多数情况下就是老年代) 进行分配担保。在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 - XX:HandlePromotionFailure 参数的设置值是否允许担保失败 (Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者-XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次 Full GC。 查看全部
一文详解JVM垃圾收集机制,动图帮你轻松理解大厂面试难点
前言
上篇文章已经给大家介绍了 JVM 的架构和运行时数据区 (内存区域),本篇文章将给大家介绍 JVM 的重点内容——垃圾收集。众所周知,相比 C / C++ 等语言,Java 可以省去手动管理内存的繁琐操作,很大程度上解放了 Java 程序员的生产力,而这正是得益于 JVM 的垃圾收集机制和内存分配策略。我们平时写程序时并感知不到这一点,但是如果是在生产环境中,JVM 的不同配置对于服务器性能的影响是非常大的,所以掌握 JVM 调优是高级 Java 工程师的必备技能。正所谓“基础不牢,地动山摇”,在这之前我们先来了解一下底层的 JVM 垃圾收集机制。
既然要介绍垃圾收集机制,就要搞清楚以下几个问题:
哪些内存区域需要进行垃圾收集?
如何判断对象是否可回收?
新的对象是如何进行内存分配的?
如何进行垃圾收集?
本文将按以下行文结构展开,对上述问题一一解答。
需要进行垃圾收集的内存区域;
判断对象是否可回收的方法;
主流的垃圾收集算法介绍;
JVM 的内存分配与垃圾收集机制。
下面开始正文,还是图文并茂的老配方,走起。
一、需要进行垃圾收集的内存区域
先来回顾一下 JVM 的运行时数据区:
JVM 运行时数据区
其中程序计数器、Java 虚拟机栈和本地方法栈都是线程私有的,与其对应的线程是共生关系,随线程而生,随线程而灭,栈中的栈帧也随着方法的进入和退出井然有序地进行入栈和出栈操作。所以这几个区域的内存分配和回收都是有很大确定性的,在方法结束或线程结束时,内存也会随之释放,因此也就不需要考虑这几个区域的内存回收问题了。
而堆和方法区就不一样了,Java 的对象几乎都是在堆上创建出来的,方法区则存储了被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,方法区中的运行时常量池则存放了各种字面量与符号引用,上述的这些数据大部分都是在运行时才能确定的,所以需要进行动态的内存管理。
还要说明一点,JVM 中的垃圾收集器的最主要的关注对象是 Java 堆,因为这里进行垃圾收集的“性价比”是最高的,尤其是在新生代 (后文对分代算法进行介绍) 中的垃圾收集,一次就可以回收 70% - 99% 的内存。而方法区由于垃圾收集判定条件,尤其是类型卸载的判定条件相当苛刻,其回收性价比是非常低的,因此有些垃圾收集器就干脆不支持或不完全支持方法区的垃圾收集,比如 JDK 11 中的 ZGC 收集器就不支持类型卸载。
二、判断对象是否可回收的方法2.1 引用计数法
引用计数法的实现很简单,在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。大部分情况下这个方法是可以发挥作用的,但是在存在循环引用的情况下,引用计数法就无能为力了。比如下面这种情况:
public class Student {<br /> // friend 字段<br /> public Student friend = null;<br /> <br /> public static void test() {<br /> Student a = new Student();<br /> Student b = new Student();<br /> a.friend = b;<br /> b.friend = a;<br /> a = null;<br /> b = null;<br /> System.gc();<br /> }<br />}
上述代码创建了 a 和 b 两个 Student 实例,并把它们各自的 friend 字段赋值为对方,除此之外,这两个对象再无任何引用,然后将它们都赋值为 null,在这种情况下,这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也就无法回收它们。如下图所示:
循环引用
但是在 Java 程序中,a 和 b 是可以被回收的,因为 JVM 并没有使用引用计数法判定对象是否可回收,而是采用了可达性分析法。
2.2 可达性分析法
这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集 (GC Root Set),从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链” (Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,则说明此对象不再被使用,也就可以被回收了。要进行可达性分析就需要先枚举根节点 (GC Roots),在枚举根节点过程中,为防止对象的引用关系发生变化,需要暂停所有用户线程 (垃圾收集之外的线程),这种暂停全部用户线程的行为被称为 (Stop The World)。可达性分析法如下图所示:
可达性分析法
图中绿色的都是位于 GC Root Set 中的 GC Roots,所有与其有关联的对象都是可达的,被标记为蓝色,而所有与其没有任何关联的对象都是不可达的,被标记为灰色。即使是不可达对象,也并非一定会被回收,如果该对象同时满足以下几个条件,那么它仍有“逃生”的可能:
该对象有重写的finalize()方法 (Object 类中的方法);
finalize()方法中将其自身链接到了引用链上;
JVM 此前没有调用过该对象的finalize()方法 (因为 JVM 在收集可回收对象时会调用且仅调用一次该对象的finalize()方法)。
不过由于finalize()方法的运行代价高昂,不确定性大,且无法保证各个对象的调用顺序,所以并不推荐使用。那么 GC Roots 又是何方神圣呢?在 Java 语言中,固定可作为GC Roots的对象包括以下几种:
在虚拟机栈 (栈帧中的本地变量表) 中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。
在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用。
在本地方法栈中JNI (即通常所说的Native方法) 引用的对象。
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常见的异常对象 (比如
NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器。
所有被同步锁 (synchronized关键字) 持有的对象。
反映Java虚拟机内部情况的 JM XBean、JVM TI 中注册的回调、本地代码缓存等。
三、垃圾收集算法介绍3.1 标记-清除算法
标记-清除算法的思想很简单,顾名思义,该算法的过程分为标记和清除两个阶段:首先标记出所有需要回收的对象,其中标记过程就是使用可达性分析法判断对象是否属于垃圾的过程。在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。示意图如下:
标记清除算法
这个算法虽然很简单,但是有两个明显的缺点:
执行效率不稳定。如果 Java 堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
导致内存空间碎片化。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作,非常影响程序运行效率。
3.2 标记-复制算法
标记-复制算法常简称复制算法,这一算法正好解决了标记-清除算法在面对大量可回收对象时执行效率低下的问题。其实现方法也很易懂:在可用内存中划分出两块大小相同的区域,每次只使用其中一块,另一块保持空闲状态,第一块用完的时候,就把存活的对象全部复制到第二块区域,然后把第一块全部清空。如下图所示:
标记-复制算法
这个算法很适合用于对象存活率低的情况,因为它只关注存活对象而无需理会可回收对象,所以 JVM 中新生代的垃圾收集正是采用的这一算法。但是其缺点也很明显,每次都要浪费一半的内存,未免太过奢侈,不过 JVM 中的新生代有更精细的内存划分,比较好地解决了这个问题,见下文。
3.3 标记-整理算法
这个算法完美解决了标记-清除算法的空间碎片化问题,其标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记整理算法
这个算法虽然可以很好地解决空间碎片化问题,但是每次垃圾回收都要移动存活的对象,还要对引用这些对象的地方进行更新,对象移动的操作也需要全程暂停用户线程 (Stop The World)。
3.4 分代收集算法
与其说是算法,不如说是理论。如今大多数虚拟机的实现版本都遵循了“分代收集”的理论进行设计,这个理论可以看作是经验之谈,因为开发人员在开发过程中发现了 JVM 中存活对象的数量和它们的年龄之间有着某种规律,如下图:
JVM 中存活对象数量与年龄之间的关系
在此基础上,人们提出了以下假说:
绝大多数对象都是朝生夕灭的。
熬过越多次垃圾收集过程的对象就越难以消亡。
根据这两个假说,可以把 JVM 的堆内存大致分为新生代和老年代,新生代对象大多存活时间短,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间,所以这一区域一般采用标记-复制算法进行垃圾收集,频率比较高。而老年代则是一些难以消亡的对象,可以采用标记-清除和标记整理算法进行垃圾收集,频率可以低一些。
按照 Hotspot 虚拟机的实现,针对新生代和老年代的垃圾收集又分为不同的类型,也有不同的名词,如下:
部分收集 (Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:新生代收集 (Minor GC / Young GC):指目标只是新生代的垃圾收集。老年代收集 (Major GC / Old GC):指目标只是老年代的垃圾收集,目前只有CMS收集器的并发收集阶段是单独收集老年代的行为。混合收集 (Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
整堆收集 (Full GC):收集整个Java堆和方法区的垃圾收集。
人们经常会混淆 Major GC 和 Full GC,不过这也有情可原,因为这两种 GC 行为都包含了老年代的垃圾收集,而单独的老年代收集 (Major GC) 又比较少见,大多数情况下只要包含老年代收集,就会是整堆收集 (Full GC),不过还是分得清楚一点比较好哈。
四、JVM 的内存分配和垃圾收集机制
经过前面的铺垫,现在终于可以一窥 JVM 的内存分配和垃圾收集机制的真面目了。
4.1 JVM 堆内存的划分
JVM 堆内存划分,从Java 8开始不再有永久代
Java 堆是 JVM 所管理的内存中最大的一块,也是垃圾收集器的管理区域。大多数垃圾收集器都会将堆内存划分为上图所示的几个区域,整体分为新生代和老年代,比例为 1 : 2,新生代又进一步分为 Eden、From Survivor 和 To Survivor,默认比例为 8 : 1 : 1,请注意,可通过 SurvivorRatio 参数进行设置。请注意,从 JDK 8 开始,JVM 中已经不再有永久代的概念了。Java 堆上的无论哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
4.2 分代收集原理4.2.1 新生代中对象的分配与回收
大多数情况下,对象优先在新生代 Eden 区中分配,当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。Eden、From Survivor 和 To Survivor 的比例为 8 : 1 : 1,之所以按这个比例是因为绝大多数对象都是朝生夕灭的,垃圾收集时 Eden 存活的对象数量不会太多,Survivor 空间小一点也足以容纳,每次新生代中可用内存空间为整个新生代容量的90% (Eden 的 80% 加上 To Survivor 的 10%),只有From Survivor 空间,即 10% 的新生代是会被“浪费”的。不会像原始的标记-复制算法那样浪费一半的内存空间。From Survivor 和 To Survivor 的空间并不是固定的,而是在 S0 和 S1 之间动态转换的,第一次 Minor GC 时会选择 S1 作为 To Survivor,并将 Eden 中存活的对象复制到其中,并将对象的年龄加1,注意新生代使用的垃圾收集算法是标记-复制算法的改良版。下面是示意图,请注意其中第一步的变色是为了醒目,虚拟机只做了标记存活对象的操作。
第一次 Minor GC 示意图
在后续的 Minor GC 中,S0 和 S1会交替转化为 From Survivor 和 To Survivor,Eden 和 From Survivor 中的存活对象会复制到 To Survivor 中,并将年龄加大 1。如下图所示:
后续 Minor GC 示意图
4.2.2 对象晋升老年代
在以下这些情况下,对象会晋升到老年代。
长期存活对象将进入老年代
对象在 Survivor 区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 设置,这个参数的最大值是15,因为对象年龄信息储存在对象头中,占4个比特 (bit)的内存,所能表示最大数字就是15。
长期存活对象晋升老年代示意图
2.大对象可以直接进入老年代
对于大对象,尤其是很长的字符串,或者元素数量很多的数组,如果分配在 Eden 中,会很容易过早占满 Eden 空间导致 Minor GC,而且大对象在 Eden 和两个 Survivor 之间的来回复制也还会有很大的内存复制开销。所以我们可以通过设置 -XX:PretenureSizeThreshold 的虚拟机参数让大对象直接进入老年代。
3.动态对象年龄判断
为了能更好地适应不同程序的内存状况,HotSpot 虚拟机并不是永远要求对象的年龄必须达到 -XX:MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 -XX:MaxTenuringThreshold 中要求的年龄。
4.空间分配担保 (Handle Promotion)
当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域 (实际上大多数情况下就是老年代) 进行分配担保。在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minor GC 可以确保是安全的。如果不成立,则虚拟机会先查看 - XX:HandlePromotionFailure 参数的设置值是否允许担保失败 (Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者-XX: HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次 Full GC。
如何有效的采集微信公众号文章
采集交流 • 优采云 发表了文章 • 0 个评论 • 124 次浏览 • 2022-05-05 05:10
有个人公众号的用户一般也有其他自媒体账号,在花费了很长时间写好了一篇文章以后终于可以放松一下,但是想着还有好几个其他平台需要复制粘贴着实头大,这个工作既没有技术含量也提现不出文笔。我们下面分享几种获取公众号的方式。
第一种:人工-手动复制粘贴
这也是最笨的一种方法,手动切换平台登录,然后打开编辑器复制粘贴,然后手动发布。
优势:如果有错误一目了然,不同的平台有不同的限制,比如标题字数,封面图片等。
劣势:手动操作浪费人力,效率低下。
第二种:手动-用数据采集工具
下载数据采集工具,手动输入链接可以下载文章内容,然后导出成word或者其他格式。
优势:无需技术配合
劣势:需要手动操作,先获取内容,然后导出,然后登录自己平台手动导入。
第三种:技术-抓取(走搜狗)
可以在程序中做定时获取固定公众号同步文章操作,通过搜狗浏览器搜索微信号,得到文章列表,然后文章列表中有文章的url,得到url后通过程序下载文章内容然后可以继续后续操作。
优势:减少人工操作,可实现半自动化自动发布
劣势:如果公众号很多,请求太频繁会有验证码出现,程序很难自动识别,一旦验证码还需要手动验证。而且无法实时同步,即公众号发完后不会立马同步。具体延时多少根据自己的请求频率决定。还有一点就是获取的链接是临时链接,必须在有效时间范围内把文章内容请求下来。
第四种:技术-抓取(走微信后台)
在微信后台文章编辑中有一个插入超链接的功能。通过这个接口可以搜索公众号,然后得到历史文章,得到文章链接后可以通过后台程序下载下来。
优势:减少人工操作,可实现半自动化自动发布。此链接是永久链接,任何时候都可用。
劣势:同第二种如果请求太频繁也会有拦截导致接口无法调用。
第五种:技术-通过一键建站推送
你只需要在一键建站平台中开通数据采集功能,配置好你接收数据的接口,一旦公众号中有新的文章发布将给你推送最新的内容
优势:延时时间短。
操作简单代码量少再也不用担心技术无法实现。
真正实现完全托管,全自动化。
劣势:付费版,免费额度很少。但是价格也好像不贵几分钱一条吧。
以上几种方式都已经亲测过了,如果有更多更好的方式记得联系我,我也好去试试,如果有需要帮助或者技术不明白的可以加我沟通交流。 查看全部
如何有效的采集微信公众号文章
有个人公众号的用户一般也有其他自媒体账号,在花费了很长时间写好了一篇文章以后终于可以放松一下,但是想着还有好几个其他平台需要复制粘贴着实头大,这个工作既没有技术含量也提现不出文笔。我们下面分享几种获取公众号的方式。
第一种:人工-手动复制粘贴
这也是最笨的一种方法,手动切换平台登录,然后打开编辑器复制粘贴,然后手动发布。
优势:如果有错误一目了然,不同的平台有不同的限制,比如标题字数,封面图片等。
劣势:手动操作浪费人力,效率低下。
第二种:手动-用数据采集工具
下载数据采集工具,手动输入链接可以下载文章内容,然后导出成word或者其他格式。
优势:无需技术配合
劣势:需要手动操作,先获取内容,然后导出,然后登录自己平台手动导入。
第三种:技术-抓取(走搜狗)
可以在程序中做定时获取固定公众号同步文章操作,通过搜狗浏览器搜索微信号,得到文章列表,然后文章列表中有文章的url,得到url后通过程序下载文章内容然后可以继续后续操作。
优势:减少人工操作,可实现半自动化自动发布
劣势:如果公众号很多,请求太频繁会有验证码出现,程序很难自动识别,一旦验证码还需要手动验证。而且无法实时同步,即公众号发完后不会立马同步。具体延时多少根据自己的请求频率决定。还有一点就是获取的链接是临时链接,必须在有效时间范围内把文章内容请求下来。
第四种:技术-抓取(走微信后台)
在微信后台文章编辑中有一个插入超链接的功能。通过这个接口可以搜索公众号,然后得到历史文章,得到文章链接后可以通过后台程序下载下来。
优势:减少人工操作,可实现半自动化自动发布。此链接是永久链接,任何时候都可用。
劣势:同第二种如果请求太频繁也会有拦截导致接口无法调用。
第五种:技术-通过一键建站推送
你只需要在一键建站平台中开通数据采集功能,配置好你接收数据的接口,一旦公众号中有新的文章发布将给你推送最新的内容
优势:延时时间短。
操作简单代码量少再也不用担心技术无法实现。
真正实现完全托管,全自动化。
劣势:付费版,免费额度很少。但是价格也好像不贵几分钱一条吧。
以上几种方式都已经亲测过了,如果有更多更好的方式记得联系我,我也好去试试,如果有需要帮助或者技术不明白的可以加我沟通交流。
网络爬虫实例系列 —— 搜狗微信文章采集方案
采集交流 • 优采云 发表了文章 • 0 个评论 • 248 次浏览 • 2022-05-05 04:05
微信和搜狗合作推出微信搜索后,让各家媒体检测平台如获珍宝,终于可以获取到微信的文章数据了;但好景不长,由于大家基本只有这一个接口来获取微信的数据,导致搜狗的压力倍增,先后出现减少返回搜索结果条数,反爬虫输入验证码的情况,导致大家又都很难抓取微信数据;不过后来搜狗又针对登录用户推出了关键字订阅功能,结合这些情况,下面梳理了采集搜狗微信文章的两个方案,仅供参考。
方案一:利用搜狗微信文章搜索接口
调用如下的URL便可访问搜狗微信关键字搜索文章的结果,其中参数query为搜索关键字,值为UTF-8编码的字符串。该URL返回发结果为10条文章的搜索结果,可以直接解析到搜索出的文章名、文章URL、公众号及摘要等信息。修改page参数的值,便可以进行翻页,得到更多的搜索结果。
%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB&sut=13695&lkt=0%2C0%2C0&type=2&sst0=68&page=1&ie=utf8&w=01019900&dr=1
优点:
实现简单,搜索的结果和关键字匹配程度比较高
缺点:
最多只能翻10页,而且网站反爬虫导致屏蔽比较严重
方案二:利用搜狗微信提供的关键字订阅接口
在搜狗微信中,对于登录的用户,搜索的关键字可以添加到订阅列表中,下次可以直接查看自己订阅关键字相关的文章。该功能限制每个登录用户最多可以添加20个订阅关键字。
A. 下面这个URL为获取登录用户已经订阅关键字的列表,其中包括关键字和对应的wordId,该ID可以用于后续访问该关键字在订阅中相关的文章。其中登录用户的标识是注册的帐号,一参数uid传入。
uid=yongzhong15%&_=28
B. 下面这个URL为指定的帐号添加订阅关键字的接口,通过该接口,可以填加帐号订阅的关键字。
uid=yongzhong15%&word=%E5%9B%BE%E8%AE%BA&_=71
C. 下面这个URL为指定的帐号删除指定的关键字,其中关键字是指订阅关键字列表中对应的wordId来指定
uid=yongzhong15%&id=49529&_=72
D. 下面这个URL返回指定用户订阅的其中一个关键字的文章列表,其中文件列表根据start参数进行翻页,关键字是使用wordid来指定。
uid=yongzhong15%&start=0&num=10&wordid=49528&clear=1&_=41
根据以上A/B/C/D四类API接口,便可以查询注册的帐号下的订阅关键字列表,添加、删除帐号下的关键字,并可以获取到各个订阅关键字对应的文章信息。
优点:
订阅API由搜狗官方免费提供,不会被屏蔽
缺点:
每个帐号最多只能订阅20个关键字,使用不方便;而且对于每个关键字,返回的文章个数远少于直接从搜索接口搜索到的相关文章数,即获取到的数据不全。 查看全部
网络爬虫实例系列 —— 搜狗微信文章采集方案
微信和搜狗合作推出微信搜索后,让各家媒体检测平台如获珍宝,终于可以获取到微信的文章数据了;但好景不长,由于大家基本只有这一个接口来获取微信的数据,导致搜狗的压力倍增,先后出现减少返回搜索结果条数,反爬虫输入验证码的情况,导致大家又都很难抓取微信数据;不过后来搜狗又针对登录用户推出了关键字订阅功能,结合这些情况,下面梳理了采集搜狗微信文章的两个方案,仅供参考。
方案一:利用搜狗微信文章搜索接口
调用如下的URL便可访问搜狗微信关键字搜索文章的结果,其中参数query为搜索关键字,值为UTF-8编码的字符串。该URL返回发结果为10条文章的搜索结果,可以直接解析到搜索出的文章名、文章URL、公众号及摘要等信息。修改page参数的值,便可以进行翻页,得到更多的搜索结果。
%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB&sut=13695&lkt=0%2C0%2C0&type=2&sst0=68&page=1&ie=utf8&w=01019900&dr=1
优点:
实现简单,搜索的结果和关键字匹配程度比较高
缺点:
最多只能翻10页,而且网站反爬虫导致屏蔽比较严重
方案二:利用搜狗微信提供的关键字订阅接口
在搜狗微信中,对于登录的用户,搜索的关键字可以添加到订阅列表中,下次可以直接查看自己订阅关键字相关的文章。该功能限制每个登录用户最多可以添加20个订阅关键字。
A. 下面这个URL为获取登录用户已经订阅关键字的列表,其中包括关键字和对应的wordId,该ID可以用于后续访问该关键字在订阅中相关的文章。其中登录用户的标识是注册的帐号,一参数uid传入。
uid=yongzhong15%&_=28
B. 下面这个URL为指定的帐号添加订阅关键字的接口,通过该接口,可以填加帐号订阅的关键字。
uid=yongzhong15%&word=%E5%9B%BE%E8%AE%BA&_=71
C. 下面这个URL为指定的帐号删除指定的关键字,其中关键字是指订阅关键字列表中对应的wordId来指定
uid=yongzhong15%&id=49529&_=72
D. 下面这个URL返回指定用户订阅的其中一个关键字的文章列表,其中文件列表根据start参数进行翻页,关键字是使用wordid来指定。
uid=yongzhong15%&start=0&num=10&wordid=49528&clear=1&_=41
根据以上A/B/C/D四类API接口,便可以查询注册的帐号下的订阅关键字列表,添加、删除帐号下的关键字,并可以获取到各个订阅关键字对应的文章信息。
优点:
订阅API由搜狗官方免费提供,不会被屏蔽
缺点:
每个帐号最多只能订阅20个关键字,使用不方便;而且对于每个关键字,返回的文章个数远少于直接从搜索接口搜索到的相关文章数,即获取到的数据不全。
连载|浅谈红队中的外网信息收集(一)
采集交流 • 优采云 发表了文章 • 0 个评论 • 69 次浏览 • 2022-05-01 14:10
前言
最近在对以往所学习的有关红队的知识点进行梳理总结,这里主要参考了 ATT&CK 矩阵模型,不过对其进行了简化,同时加入了一些国内特有的情况放了进去。
大体上会按照外网信息收集、打点、权限维持、提权、内网信息收集、横向移动、痕迹清理这样的顺序展开。
因为是梳理总结性的文章,所以文章的侧重点在于「面」而不在于具体的某个「点」,因此文中的具体技术细节不会展开去谈,不然内容会很多。
想了解具体细节的读者可以去看看我的个人公众号 TeamsSix 里的历史文章,里面会有针对一些点展开描述的文章。
受限于个人水平,文中难免会出现错误或者描述不当的地方,还望在评论处指出,望谅解。
确定目标
当开始做信息收集之前,肯定是要先确定目标的,在红队项目或者 HW 项目中,一般目标都是一个公司的名称,然后通过这个公司的名称获取各种信息,接着开展外网打点、内网渗透等等工作。
在我们得知目标公司名称后,就可以开展信息收集的工作了。
外网信息收集
我这里梳理了大概以下这些信息收集的方式,当然肯定是不全的,欢迎大家在评论区一起补充讨论:
1、组织股权结构
拿到公司名称后,先不用急着查备案、找域名,而是先看看这家公司的股权构成,因为一般一家公司的子公司也是可以作为目标去打的,不过有时是要求 50% 持股或者 100% 持股,这个就要看具体实际情况了。
比较常见的查询公司组织股权结构的网站有天眼查、企查查、爱企查、小蓝本、钉钉企典等等。
如果目标持股公司不多,可以直接看股权穿透图,比较直观;如果持股公司比较多,股权穿透图看着就比较费力了。
除了股权穿透之外,还可以看它的对外投资信息
这两个地方都可以查到有目标持股的公司。
如果目标比较少一个一个子公司的去看还好,但如果目标很多,那这效率就很低了,好在现在也有了现成的工具。
ENScanGo
ENScanGo 是现有开源项目 ENScan 的升级版本,工具地址:
这是一款由狼组安全团队的 Keac 师傅写的专门用来解决企业信息收集难的问题的工具,可以一键收集目标及其控股公司的 ICP 备案、APP、小程序、微信公众号等信息然后聚合导出。
例如我这里搜集「北京百度网讯科技有限公司」以及他持股了 50% 的公司信息。
enscan -n 北京百度网讯科技有限公司 -invest-num 50
收集后的结果如下:
这样一来,直接省去了收集 ICP 备案的步骤,一键获得了目标公司及其子公司的公司名称、app、微信公众号、ICP 备案等信息。
2、主域名查询
主域名查询可以分为备案域名查询和未备案域名查询。
备案域名查询
除了上面从企业信息查询网站中获取到备案信息外,最全也是最准确的方法就是去国家的备案信息查询网站里查询了,地址为:。
除了官方的渠道外,还有一些第三方的备案域名查询站点,比如站长之家等等。
未备案域名查询
有些企业会把自己的其他业务站点放在网站尾部,里面也许会包含未备案的站点。
3、子域获取
比较常见的工具就是 OneForAll,除此之外还有 amass、subfinder、xray、ksubdomain 等等。
如果提前知道目标,还可以提前收集一波子域,然后项目快开始的时候,再收集一波子域,将两次收集的结果做下对比,优先打新增子域。
4、端口扫描
一般比较常见的可能就是 nmap 和 masscan 了,这里分享一个 nmap 快速扫描全部端口的命令。
nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 -iL ip.txt -oX output.xml
不过除了这些方法外,fscan 其实也可以拿来做外网的端口扫描,而且速度更快。
比如用 fscan 只扫描端口,不做漏洞扫描
fscan -hf hosts.txt --nopoc -t 100
fscan 默认线程是 600,因为是外网扫描 600 的线程就比较大了,所以这里设置了 100,如果感觉扫描结果不理想,线程还可以设置的再小点。
端口扫描可以结合空间搜索引擎的结果,如果通过空间搜索引擎发现目标存在很多的高位端口,那么在进行端口扫描的时候就要记得也把这些高位端口加上。
5、指纹识别
指纹识别是我个人觉着非常重要的一点,因为指纹识别的结果对打点的帮助是很大的,可以让打点更有针对性,同时也会节省很多时间。
比较常见的在线指纹查询网站有 godeye 和云悉等,工具有 observer_ward 和 Ehole 等。
6、空间搜索引擎
擅用空间搜索引擎,有时可以在最初刚拿确定目标的时候就发现目标弱点。
目前比较常见的空间搜索引擎有 Fofa、Shodan、360 夸克、奇安信全球鹰、知道创宇 ZoomEye 等等。
常见的工具有 Fofa_Viewer、FofaX、Kunyu,其中 Fofa_Viewer 为图形化界面,使用友好。
下载地址:
FofaX 为命令行界面,FofaX 可以结合其他工具联动使用,除了这两个工具调用 Fofa 接口外,上面提到的 Ehole 也可以调用 Fofa 查询。
Kunyu 调用的是 ZoomEye 的接口,工具下载地址:
7、api 接口
获取 api 接口常用的工具有 jsinfo、findsomething、jsfinder、BurpJSLinkFinder 等等。
如果找到了一些未授权接口,也许就可以搞到一些高价值信息,比如大量敏感信息泄露之类的。
另外在翻 js 文件的时候,可以关注下有没有以 runtime 命名的 js 文件,因为在这种 js 文件中会包含其他 js 文件的名称(包括当前页面没有加载的 js 文件),这样利用 runtime js 文件就发现了更多的 js 文件,使得找到 api 接口的概率又大了些。
8、目录获取
目录扫描比较常用的工具有 dirsearch、ffuf
ffuf 更侧重于 FFUZ,不过不管是目录扫描还是 FFUZ ,扫描的结果都在于字典,Github 上 4k 多个 star 的字典:
9、邮箱地址获取
邮箱地址比较常用的方法有直接通过搜索引擎找网上公开的邮箱信息,这种往往会指向目标的网站中,比如目标某个网页的附件中包含有邮箱等信息。
之外还可以使用 Github 搜索目标公司开发者在代码中注释的邮箱信息,其实不太明白为什么开发者都喜欢把自己的邮箱注释到代码里。
也可以通过领英找到目标公司的员工姓名,通过「拼音+@公司域名」的方法去构造员工邮箱。
也有一些网站可以查询邮箱,这种比较方便,比如以下网站:
另外如果收集到了目标的 outlook 站点,也可以尝试去爆破邮箱用户名。
10、网盘信息
网盘信息里有时也会发现不少好东西,这类网站也很多,可以在爱达杂货铺导航站里找到很多网盘搜索类站点。
11、其他信息
其他的信息比如 app、小程序、供应商、外包合作商、公众号等,或多或少都可以从上面的组织股权架构类网站中查询到,或者使用 ENScan 也可以。
其中比较值得注意是的供应商和外包合作商,如果拿下供应商也许可以直接进入目标内网,如果拿下外包合作商则可以借助这一层关系进行社工或者尝试进入目标内网等操作。
后记
红队中的信息收集当然远不止上面说到的,其他比较常用的还有资产监控平台、社工库、自动化信息收集工具(比如 ShuiZe)以及各种内部红队平台等等,这里篇幅有限就不再展开了。 查看全部
连载|浅谈红队中的外网信息收集(一)
前言
最近在对以往所学习的有关红队的知识点进行梳理总结,这里主要参考了 ATT&CK 矩阵模型,不过对其进行了简化,同时加入了一些国内特有的情况放了进去。
大体上会按照外网信息收集、打点、权限维持、提权、内网信息收集、横向移动、痕迹清理这样的顺序展开。
因为是梳理总结性的文章,所以文章的侧重点在于「面」而不在于具体的某个「点」,因此文中的具体技术细节不会展开去谈,不然内容会很多。
想了解具体细节的读者可以去看看我的个人公众号 TeamsSix 里的历史文章,里面会有针对一些点展开描述的文章。
受限于个人水平,文中难免会出现错误或者描述不当的地方,还望在评论处指出,望谅解。
确定目标
当开始做信息收集之前,肯定是要先确定目标的,在红队项目或者 HW 项目中,一般目标都是一个公司的名称,然后通过这个公司的名称获取各种信息,接着开展外网打点、内网渗透等等工作。
在我们得知目标公司名称后,就可以开展信息收集的工作了。
外网信息收集
我这里梳理了大概以下这些信息收集的方式,当然肯定是不全的,欢迎大家在评论区一起补充讨论:
1、组织股权结构
拿到公司名称后,先不用急着查备案、找域名,而是先看看这家公司的股权构成,因为一般一家公司的子公司也是可以作为目标去打的,不过有时是要求 50% 持股或者 100% 持股,这个就要看具体实际情况了。
比较常见的查询公司组织股权结构的网站有天眼查、企查查、爱企查、小蓝本、钉钉企典等等。
如果目标持股公司不多,可以直接看股权穿透图,比较直观;如果持股公司比较多,股权穿透图看着就比较费力了。
除了股权穿透之外,还可以看它的对外投资信息
这两个地方都可以查到有目标持股的公司。
如果目标比较少一个一个子公司的去看还好,但如果目标很多,那这效率就很低了,好在现在也有了现成的工具。
ENScanGo
ENScanGo 是现有开源项目 ENScan 的升级版本,工具地址:
这是一款由狼组安全团队的 Keac 师傅写的专门用来解决企业信息收集难的问题的工具,可以一键收集目标及其控股公司的 ICP 备案、APP、小程序、微信公众号等信息然后聚合导出。
例如我这里搜集「北京百度网讯科技有限公司」以及他持股了 50% 的公司信息。
enscan -n 北京百度网讯科技有限公司 -invest-num 50
收集后的结果如下:
这样一来,直接省去了收集 ICP 备案的步骤,一键获得了目标公司及其子公司的公司名称、app、微信公众号、ICP 备案等信息。
2、主域名查询
主域名查询可以分为备案域名查询和未备案域名查询。
备案域名查询
除了上面从企业信息查询网站中获取到备案信息外,最全也是最准确的方法就是去国家的备案信息查询网站里查询了,地址为:。
除了官方的渠道外,还有一些第三方的备案域名查询站点,比如站长之家等等。
未备案域名查询
有些企业会把自己的其他业务站点放在网站尾部,里面也许会包含未备案的站点。
3、子域获取
比较常见的工具就是 OneForAll,除此之外还有 amass、subfinder、xray、ksubdomain 等等。
如果提前知道目标,还可以提前收集一波子域,然后项目快开始的时候,再收集一波子域,将两次收集的结果做下对比,优先打新增子域。
4、端口扫描
一般比较常见的可能就是 nmap 和 masscan 了,这里分享一个 nmap 快速扫描全部端口的命令。
nmap -sS -Pn -n --open --min-hostgroup 4 --min-parallelism 1024 --host-timeout 30 -T4 -v -p 1-65535 -iL ip.txt -oX output.xml
不过除了这些方法外,fscan 其实也可以拿来做外网的端口扫描,而且速度更快。
比如用 fscan 只扫描端口,不做漏洞扫描
fscan -hf hosts.txt --nopoc -t 100
fscan 默认线程是 600,因为是外网扫描 600 的线程就比较大了,所以这里设置了 100,如果感觉扫描结果不理想,线程还可以设置的再小点。
端口扫描可以结合空间搜索引擎的结果,如果通过空间搜索引擎发现目标存在很多的高位端口,那么在进行端口扫描的时候就要记得也把这些高位端口加上。
5、指纹识别
指纹识别是我个人觉着非常重要的一点,因为指纹识别的结果对打点的帮助是很大的,可以让打点更有针对性,同时也会节省很多时间。
比较常见的在线指纹查询网站有 godeye 和云悉等,工具有 observer_ward 和 Ehole 等。
6、空间搜索引擎
擅用空间搜索引擎,有时可以在最初刚拿确定目标的时候就发现目标弱点。
目前比较常见的空间搜索引擎有 Fofa、Shodan、360 夸克、奇安信全球鹰、知道创宇 ZoomEye 等等。
常见的工具有 Fofa_Viewer、FofaX、Kunyu,其中 Fofa_Viewer 为图形化界面,使用友好。
下载地址:
FofaX 为命令行界面,FofaX 可以结合其他工具联动使用,除了这两个工具调用 Fofa 接口外,上面提到的 Ehole 也可以调用 Fofa 查询。
Kunyu 调用的是 ZoomEye 的接口,工具下载地址:
7、api 接口
获取 api 接口常用的工具有 jsinfo、findsomething、jsfinder、BurpJSLinkFinder 等等。
如果找到了一些未授权接口,也许就可以搞到一些高价值信息,比如大量敏感信息泄露之类的。
另外在翻 js 文件的时候,可以关注下有没有以 runtime 命名的 js 文件,因为在这种 js 文件中会包含其他 js 文件的名称(包括当前页面没有加载的 js 文件),这样利用 runtime js 文件就发现了更多的 js 文件,使得找到 api 接口的概率又大了些。
8、目录获取
目录扫描比较常用的工具有 dirsearch、ffuf
ffuf 更侧重于 FFUZ,不过不管是目录扫描还是 FFUZ ,扫描的结果都在于字典,Github 上 4k 多个 star 的字典:
9、邮箱地址获取
邮箱地址比较常用的方法有直接通过搜索引擎找网上公开的邮箱信息,这种往往会指向目标的网站中,比如目标某个网页的附件中包含有邮箱等信息。
之外还可以使用 Github 搜索目标公司开发者在代码中注释的邮箱信息,其实不太明白为什么开发者都喜欢把自己的邮箱注释到代码里。
也可以通过领英找到目标公司的员工姓名,通过「拼音+@公司域名」的方法去构造员工邮箱。
也有一些网站可以查询邮箱,这种比较方便,比如以下网站:
另外如果收集到了目标的 outlook 站点,也可以尝试去爆破邮箱用户名。
10、网盘信息
网盘信息里有时也会发现不少好东西,这类网站也很多,可以在爱达杂货铺导航站里找到很多网盘搜索类站点。
11、其他信息
其他的信息比如 app、小程序、供应商、外包合作商、公众号等,或多或少都可以从上面的组织股权架构类网站中查询到,或者使用 ENScan 也可以。
其中比较值得注意是的供应商和外包合作商,如果拿下供应商也许可以直接进入目标内网,如果拿下外包合作商则可以借助这一层关系进行社工或者尝试进入目标内网等操作。
后记
红队中的信息收集当然远不止上面说到的,其他比较常用的还有资产监控平台、社工库、自动化信息收集工具(比如 ShuiZe)以及各种内部红队平台等等,这里篇幅有限就不再展开了。