Jsoup 网路爬虫 实战使用
“硬盘有价数据无价” , 程序 = 数据结构 + 算法 , 计算机的诞生时主要作用在于辅助数据运算,任何能被计算机采集存储的信息都为数据. 而数据的价值是有区别的,推荐算法,程序预警往往是基于已有数据和算法去实现的,当然最浅显的说就是值钱.
当然从另一种角度上来说,就是凡是能通过固定流程处理的东西,都可以程序化,而凡我们可以手动操作电脑或者类似设备实现的东西都可以程序化. ps: 程序不一定是最高效的解决方案,我想起有个老师问我的问题了,我可以一个 excel 解决的事情为什么要用程序 (_)
好吧,说正事,场景就是 gpt 火了,公司也做了相关的业务,大体就是利用开源项目自己训练辅助模型,弄个智能客服哪一类的,而算法那面训练的时候需要有一些在政务网可查的数据,录入到图数据库中
大数据的四个基本特征
- 数据量大(Volume)
- 数据种类多(Vari⁃ety)
- 数据价值密度低(Value)
- 以及数据产生和处理速度快(Velocity)
g 我又不是大数据科班出身的,不过总结一下这几个月的经验就是
- 数据有好有坏
- 数据来源有自己项目产生
- 其他系统接口授权(共享平台)
- 数仓权限申请
- 可以通过 技术手段/人力手段 搜集互联网公开数据
- 数据具有时效性,有些数据同步过来时间太长就没啥用了(ps:当时哥说我们自己爬,能不能不要授权,我丢,哪个数据量可是老大了,而且嗯数据保密啊,不和规矩)
- 数据具有保密性,对这个很重要,数据安全真的贼重要,每一次大规模的用户数据泄露都很麻烦,就想想吧,假如你的网购记录对你在意的人公开,或者某些嗯记录,我一直感觉我在互联网上裸奔 (社会工程 安全体系中人的漏洞 , 这本书相当推荐,细节控标示学到了)
因为要录入数据嘛,刚开始看了一下 cql 的语法,虽然记不住但类比一下大差不差照猫画虎就写了点测一下正常,然后嗯看了下数据量也就中等意思,然后就去网站手摘放到 excel 中, 用 easyexcel 导到 内存中 根据 freemarker 去拼接好处理好的 String 写入到一个 txt 文本中就好了,然后任务完成,但是隔了一段时间又有其他数据需要入库,大概是十张页面的数据的一部分,这回手动去摘抄到 excel 中很遭罪.所以使用了爬虫技术.
什么是爬虫
爬虫(Web Crawler)是一种自动化程序,也称为网络爬虫、网络蜘蛛或网络机器人,它们被设计用于自动地浏览互联网上的网页并收集信息。爬虫以一种系统化的方式遍历互联网,访问网页并获取页面内容,然后将这些数据传递给搜索引擎或其他应用程序进行处理、索引或分析。
爬虫的工作原理是通过遵循超链接来跟踪网页之间的链接,从一个页面获取链接,再通过这些链接找到其他页面。它们收集的数据可以用于不同的用途,例如搜索引擎的网页索引、数据挖掘、分析和展示等。
爬虫在互联网上发挥着重要的作用,帮助搜索引擎发现、索引和提供网页,以便用户能够通过搜索引擎找到他们需要的信息。同时,爬虫也可能被用于其他目的,例如抓取数据以进行竞争情报、价格比较、内容聚合等。
看得出来,其主要作用就是针对于网路上数据
那是否能从网页上通过机器手段获取数据的都算爬虫那?
爬虫的目的也应对爬虫的方案
我也不是专业学爬虫的,只是完成任务的时候有需要,所以嗯简单带过
- 核心,爬虫是获取数据的自动化工具,不是产生数据的
- 合规,爬虫要符合相应数据提供商的规范,不可以对系统造成破化
- 不能碰机密数据,啊也不要用公开数据赚钱
上面的原则是不可改变的,不然容易吃牢饭,当然那是非专业的底线,谨小慎微足够尊重法律
简单的反扒手段
数据加密传输,防止直接通过接口请求 + 参数的形式轻松获取数据
参数限定,增加爬取难度,比如入参参数, + 预警机制
加入参参数 + 加一些不明所以的参数,嗯混淆视听增加爬取难度, 勤换参数,只要条件不同意编码起来难度剧增
身份验证: 身份验证加时效,勤刷新,数据分级做验证
日志: 请求日志记录唯一性信息 (比如用户身份 用户 ip 及时封号封 ip)
当然,处理起来越麻烦越安全,成本越高,甚至有些会影响性能
当然,直接白名单 ip + 局域网 能搞定 九成八
爬虫开发注意事项
- 减少数据获取次数,不要给他人服务器带来压力
- 多用户模式 , 不要可这一个账号爬数据,chat-gpt 刚火的时候的共享平台,就是多账号模式,通过增加账号减少被封号风险.(当然多 ip 也算一个思路了)
- 时间范围问题,当出现问题后最先查的 日志时间一定是整点数据和固定间隔数据,因为 cron 表达式写的越花越难搞. 可以加个 sleep 睡眠随机值(gpt 刚出的时候连着 AI 写情书,想做个定时群发情书邮件的代码(晚 8-10 点间发),后来成了,但是没扩展,只能单人使用,然后情书也变成了祝福,但 AI 调用上限到了之后就没有搞)
- 热门信息存储,如果不是定期的爬取的话,爬取数据不确定,可以先统计前一天查询频率高的数据,提前在对方系统调用少的时间段调用本地存储,减少热门信息对对方服务器带来的压力
- 限流,对限流,不管是定时调用 ,还是通过 sleep 来模拟人调用总会确定大概调用频率,对对方服务器的压力是有限的,但当完全时时获取数据后,我们的系统会不会被攻击,从而影响到对方系统就不一定了,所以也得做反扒,套娃;
具体实践
-
/robots.txt
这是决定吃不吃牢饭,吃多少的关键,域名 + /robots.txt ,当然这个只是建议,少量的获取数据,我不想复制黏贴那种,也没啥,嗯我正常请求啊.
-
通过 http 请求模拟用户行为获取数据,从中截取需要的
f12 看到的数据为正常所需要的数据使用这种方法,当然就是用各种工具发请求就好了,爬虫的难点在找到能用的数据,和获取相应地权限,但重中之重在于不进局子.
这段请求有好多工具都支持,核心就是模拟用户行为去发请求就好了
String body = HttpRequest.get(url2) .execute() .body();
-
如果加密了,请求回来的东西看不懂,或者是一堆代码,没有实质信息
就从渲染好的 html 文件中获取
至于 jsoup 的 api 我就随手搜了几个看了下就用了,很简单的
使用了 Java 的 jsoup 库,获取 Document ,从标签中寻找 我想要的信息,处理组装,当然这个代码仅仅是我刚写的一部分测试代码,实际上整理了一个 excel 用错需要请求的页面,还有在系统内部id 标示的字段,通过导入对象循环爬取数据写入 最后要处理的列表统一生成 cql
然后处理为我想要的格式填到 cql 模板里,其实可以直接读取 cql 执行
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.15.3</version> </dependency>
String url2 = String.format("https://www.zjzwfw.gov.cn/zjservice/item/detail/index.do?localInnerCode=9392a880-a166-4413-857b-3ff4921942c1&webId=1"); String C = "arialoadData=false; zh_choose_undefined=n; cna=BqkWHhGJEx0CAf+Z/Pm9; _ud_=16293bf89b71451aaa91d4c38ea7e088; aliyungf_tc=278dfc45157d55f9044ffe6edf9672fc1e1e7f8d3db78c61c9a933082f1ccf17; acw_tc=ac11000117039933109205031e6781887464728a3f8b2fb5b8e69092f3091a; ZJZWFWSESSIONID=50db77d6-d130-49ae-907d-edb0f550f113; session=a88e4f95cdb54bb48b37f6eaa7a894e4; webId=1; zjzw_siteCode=330000000000; acw_sc__v2=6590e003d043ff24376b4c4a8dd52e104ee3d41a; zwlogBaseinfo=eyJsbF91c2VyaWQiOiIiLCJsb2dfc3RhdHVzIjoiIiwidXNlclR5cGUiOiJndWVzdCIsImJpel9zZXNzaW9uX2lkIjoiYTg4ZTRmOTVjZGI1NGJiNDhiMzdmNmVhYTdhODk0ZTQiLCJzaXRlX2lkIjoiMzMwMDAwMDAwMDAwIiwicGFnZV9tb2RlIjoi5bi46KeE5qih5byPIn0%3D; ssxmod_itna2=Yu0=GKDK7KAIjxlSD+obdQitSpTqGqG=qr8PD6hKIDpD05awY03mPnjUXonZ8lAxAP39KemY6KYd4jHb32lOwo6APM1A8nvPc+c8cwGN9p9+6aPo6yBhq4qKIOyZINjYHl2vFzXzA7wKy4mtxxQXnDqr/b4IPp3oVe4UL9LUKTjYoPMPB4Cu3alXYhq6qK4vp/WTbs4t4d8bBGhcKP1Tj3=jNEPorspHbVCh04tfzXaODhTdWPfIM4m7hPf7IL/8NXeE+L1Y6E/ANLOR0su70qMOCDWM+P4c25fKm5fh0eEKLTDaZwjzYxaNSzaw4GioYxseWYBuziL757DADDwxqDjKDedx4D==; ssxmod_itna=eqUxuQGQeiubDX7q7xDwxmTIO766f3D/KDfx4iNDnGD8hxD5dAeLeTL7+2Cw3TKD7uA74OQ+xvreKGnRY8WDCPGnDBIhea94YYDtxBYDQxAYDGDDPDo1PD1D3qDkD7gCMBRTqi3DECKGfDDoDYSj0DitD4qDBFE3DKqGgtTYqji=dDDNdSewRRDDh/w0fietY1uD457GuD0toxBdTmPTKv6Fdjw0ak6Xr4qGyeKGumqUMU4bDCr=lILX8i3xKGGIe+x4T3xh7BDwwfBR77iYQ9x4q43qwKRKrmS8xD=="; Document doc = null; try { doc = Jsoup.connect(url2).cookie("Cookie", C).get(); } catch (IOException e) { } Elements clearfix = doc.getElementsByClass("offline-office clearfix"); ArrayList<OfflineHandlingOfPower> offlineHandlingOfPowers = new ArrayList<>(); for (Element element : clearfix) { Elements S = element.select("具体地址"); Elements clearfix1 = element.getElementsByClass("offline-office-textNew"); String metainformation = clearfix1.text(); OfflineHandlingOfPower offlineHandlingOfPower = new OfflineHandlingOfPower(); String[] lxdh = metainformation.split("联系电话:"); String[] split = lxdh[0].split("办理时间:"); String trim = split[0].replace("具体地址:", "").trim(); offlineHandlingOfPower.setQl_inner_code("1"); offlineHandlingOfPower.setName(trim); offlineHandlingOfPower.setProcess_time(split[1].trim()); offlineHandlingOfPowers.add(offlineHandlingOfPower); } offlineHandlingOfPowers.forEach(System.out::println); String projectPath = System.getProperty("user.dir"); String inputPath = projectPath + File.separator + "src/main/resources/templates/cql/CqlCreateNode.ftl"; String outputPath = projectPath + File.separator + "src/main/resources/generate/cql/createCql.txt"; // new 出 Configuration 对象,参数为 FreeMarker 版本号 Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); // 指定模板文件所在的路径 File templateDir = new File(inputPath).getParentFile(); configuration.setDirectoryForTemplateLoading(templateDir); // 设置模板文件使用的字符集 configuration.setDefaultEncoding("utf-8"); // 创建模板对象,加载指定模板 String templateName = new File(inputPath).getName(); Template template = configuration.getTemplate(templateName); StringWriter stringWriter = new StringWriter(); HashMap<String, Object> mindle = new HashMap<>(); mindle.put("entityList", offlineHandlingOfPowers); template.process(mindle, stringWriter); String generatedCode = stringWriter.toString(); // Write generated code to a file try (FileWriter fileWriter = new FileWriter(outputPath, true); PrintWriter printWriter = new PrintWriter(fileWriter)) { printWriter.println(generatedCode); } catch (IOException e) { e.printStackTrace(); }
-
如果是那种边看边加载,分小片反复验那种
洗洗睡吧,我不是专门做爬虫的
补一张图
HAR 格式的文档可能泄露个人铭感身份信息,不过,如果玩爬虫的话保存 HAR 格式文件,从其中调取用到的参数比直接上肉眼看着快得多,可以百度了解下这个文件的格式和对应记录的信息.
总结
简单的爬虫真的很简单,但是安全合规和对数据的处理反而是很麻烦的,一切用户所能见到的数据都可以通过技术手段获取,当然,其实用户看不见的数据互传也能获取,实在不行物理入侵,没有绝对安全的系统,有的只是程序员的与空气搏斗,多想到了就能多一层安全,谨慎一些就可能少吃一口皇粮,保护数据的安全和带来更好的使用体验,解放双手,解放流程性的枯燥才是编程应该实现的目标,绝不仅仅是写出更帅的代码;有些东西用户使用体验好无非就是先做一部,而这一步往往是数据做支撑,爬虫嗯友好又坏,其实过程是很枯燥的,要一点点点的猜测别人的实现,通过 浏览器的一些工具去确定参数,接口范围,猜大概实现,找到自己能用的数据,然后通过代码去清洗.
能用工具先用工具,工具不行上人工,在写这个之前我想要自动获取 cookie ,想用 selenium 去实现,但是啊适配问题,我找不到对应的版本,安装了好几,狂躁,各种问题,连续几个小时,有的时候怀疑人生,虽然最后没搞定,但是也学到了很多东西,也暴露了我嗯有些地方还是存在坑,需要回去填. 嗯共享开放平台也可以共享数据啊,硬盘有价数据无价.
不过有时候最简单的解决方案就是人工啊,哈哈哈哈哈哈;本期坑
Java 使用 neo4j
selenium 使用
http 等网络协议深入理解
数据清洗落库
数据类型和更多数据处理方面的知识