<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title><![CDATA[阿龙的学习笔记]]></title>
  <subtitle><![CDATA[兼济天下则达,独善其身则穷]]></subtitle>
  <link href="/atom.xml" rel="self"/>
  <link href="http://program.robinjia.cc/"/>
  <updated>2026-01-23T10:39:11.605Z</updated>
  <id>http://program.robinjia.cc/</id>
  
  <author>
    <name><![CDATA[robinjia]]></name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title><![CDATA[iframe特性在Web反爬中的运用]]></title>
    <link href="http://program.robinjia.cc/2026/01/23/iframe%E7%89%B9%E6%80%A7%E5%9C%A8Web%E5%8F%8D%E7%88%AC%E4%B8%AD%E7%9A%84%E8%BF%90%E7%94%A8/"/>
    <id>http://program.robinjia.cc/2026/01/23/iframe特性在Web反爬中的运用/</id>
    <published>2026-01-23T10:30:01.000Z</published>
    <updated>2026-01-23T10:39:11.605Z</updated>
    <content type="html"><![CDATA[<p>最近又发现一个很有意思的特性，和 iframe 相关。</p>
<p>iframe 是在 HTML 页面里用来嵌入其它页面的，它有个 contentWindow 属性，会和当前页面的 window 不一样。写一段测试代码看看就知道了：</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">a</span> = document.<span class="function"><span class="title">createElement</span><span class="params">(<span class="string">'iframe'</span>)</span></span></span><br><span class="line">document<span class="class">.body</span><span class="class">.appendChild</span>(a)</span><br><span class="line">console.<span class="function"><span class="title">log</span><span class="params">(a.contentWindow != window)</span></span></span><br><span class="line">console.<span class="function"><span class="title">log</span><span class="params">(a.contentWindow.console != window.console)</span></span></span><br><span class="line"><span class="tag">b</span> = document.<span class="function"><span class="title">createElement</span><span class="params">(<span class="string">'iframe'</span>)</span></span></span><br><span class="line">document<span class="class">.body</span><span class="class">.appendChild</span>(b)</span><br><span class="line">console.<span class="function"><span class="title">log</span><span class="params">(a.contentWindow != b.contentWindow)</span></span></span><br></pre></td></tr></table></figure>
<p>在浏览器里，上面三个判断都输出 true，这就说明 iframe 里的 window 和当前页面的 window 是独立的。</p>
<p>如果<a href="http://program.robinjia.cc/2026/01/23/console-table%E7%94%A8%E6%9D%A5%E6%A3%80%E6%B5%8B%E6%B5%8F%E8%A7%88%E5%99%A8%E8%87%AA%E5%8A%A8%E5%8C%96/">《console.table 用来检测浏览器自动化》</a>这篇文章里提到的 console.table 是从 iframe 里取的，那么直接修改 console.table 的方法就会失效——因为改的是当前页面里的 console.table，没法直接修改 iframe 里的 console.table。</p>
<p>如果代码是静态的还好处理：等创建好 iframe 后，再修改里面的 console.table。但如果是动态的，那就麻烦了，得再去想办法修改 iframe 里的 console.table。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>最近又发现一个很有意思的特性，和 iframe 相关。</p>
<p>iframe 是在 HTML 页面里用来嵌入其它页面的，它有个 contentWindow 属性，会和当前页面的 window 不一样。写一段测试代码看看就知道了：</p>
<figure class="]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[console.table用来检测浏览器自动化]]></title>
    <link href="http://program.robinjia.cc/2026/01/23/console-table%E7%94%A8%E6%9D%A5%E6%A3%80%E6%B5%8B%E6%B5%8F%E8%A7%88%E5%99%A8%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    <id>http://program.robinjia.cc/2026/01/23/console-table用来检测浏览器自动化/</id>
    <published>2026-01-23T10:27:35.000Z</published>
    <updated>2026-01-23T10:39:11.605Z</updated>
    <content type="html"><![CDATA[<p>在之前文章<a href="http://program.robinjia.cc/2026/01/15/%E5%90%AC%E8%AF%B4%E4%BD%A0Selenium%E5%92%8CPlaywright%E8%87%AA%E5%8A%A8%E5%8C%96%E6%97%A0%E6%95%8C%EF%BC%8C%E6%9D%A5%E8%AF%95%E8%AF%95%E8%BF%99%E4%B8%A4%E4%B8%AA%E7%AB%99%E7%82%B9%E5%90%A7/">听说你Selenium和Playwright自动化无敌，来试试这两个站点吧</a>里，我们说过 Selenium 和 Playwright 会被重定向到空白页，于是就研究了下，发现都无法打开开发者工具，这让我想到 disable-devtool，在<a href="http://program.robinjia.cc/2025/09/21/%E8%A7%A3%E5%86%B3disable-devtool%E6%8E%A7%E5%88%B6%E5%8F%B0%E5%8F%8D%E8%B0%83%E8%AF%95/">解决disable-devtool控制台反调试</a>里我们就有研究过这个库。从浏览器设置里，强制打开开发者工具，在 console 里看到好多日志，确定是 disable-devtool。于是大胆猜测，是因为 disable-devtool 导致浏览器自动化重定向了，即便没有打开开发者工具。</p>
<p>在本地写了个测试页面, 测试代码见<a href="https://github.com/dengshilong/js_reverse/blob/main/disable_devtool/test.html，用浏览器自动化去访问，果然会被重定向到空白页面。于是就去排查disable-devtool，总共就几种测试类型，那就一种一种测试下。当测试到" target="_blank" rel="external">https://github.com/dengshilong/js_reverse/blob/main/disable_devtool/test.html，用浏览器自动化去访问，果然会被重定向到空白页面。于是就去排查disable-devtool，总共就几种测试类型，那就一种一种测试下。当测试到</a> Performance 类型时，发现浏览器自动化会重定向到空白页。看了下代码，找到最终原因 console.table。</p>
<p>在没有打开开发者工具时，浏览器自动化 console.table 输出大对象时也耗时很久，和打开开发者工具时一样，所以就被判定为打开开发者工具，会被重定向。而正常浏览器在没有打开开发者工具时，console.table 输出大对象耗时很短。原因找到了，剩下的就好办了。</p>
<p>我想这就是纯纯的误伤了，disable-devtool 本意是用来检测用户打开开发者工具的，没想到把自动化工具也检测出来了。不过这确实可以作为检测自动化工具的一个因素，一旦发现console.table耗时很长，是自动化爬虫的可能就很高。</p>
<p>那么问题来了，为什么同样是自动化工具，在没有打开开发者工具时，Selenium 和 Playwright 执行 console.table 耗时很长，而 DrissionPage 却耗时很短呢？</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>在之前文章<a href="http://program.robinjia.cc/2026/01/15/%E5%90%AC%E8%AF%B4%E4%BD%A0Selenium%E5%92%8CPlaywright%E8%87%AA%E5%8A%A8%E5%8C%96%E6%]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[听说你Selenium和Playwright自动化无敌，来试试这两个站点吧]]></title>
    <link href="http://program.robinjia.cc/2026/01/15/%E5%90%AC%E8%AF%B4%E4%BD%A0Selenium%E5%92%8CPlaywright%E8%87%AA%E5%8A%A8%E5%8C%96%E6%97%A0%E6%95%8C%EF%BC%8C%E6%9D%A5%E8%AF%95%E8%AF%95%E8%BF%99%E4%B8%A4%E4%B8%AA%E7%AB%99%E7%82%B9%E5%90%A7/"/>
    <id>http://program.robinjia.cc/2026/01/15/听说你Selenium和Playwright自动化无敌，来试试这两个站点吧/</id>
    <published>2026-01-15T11:25:03.000Z</published>
    <updated>2026-01-15T11:28:26.506Z</updated>
    <content type="html"><![CDATA[<p>有道友说遇到一个 Selenium 自动化过不去的站点 aHR0cHM6Ly93d3cuemhpcGluLmNvbS9qb2JfZGV0YWlsLzgyOTI2ZjM5MDRhNjZlMDUwM1pfMHQtLUZWUlMuaHRtbA==，又有道友说遇到了一个 Playwright 过不去的 aHR0cHM6Ly9zc28uY25pcGEuZ292LmNuL2FtLyMvbG9naW4=，于是掏出自动化工具试试，毕竟之前一直认为自动化在国内无敌。</p>
<p>在 Selenium和Playwright 自动化工具里加上–disable-blink-features=AutomationControlled参数(这个参数在<a href="http://program.robinjia.cc/2025/12/23/%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E5%A5%BD%E7%94%A8%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8C%96%E5%8F%82%E6%95%B0/">一个非常好用的自动化参数</a>里写过， 不知道的可以看看)后，打开网站后都会被重定向到空白页， 这可如何是好。<br>既然 Selenium 和 Playwright 自动化都过不去，那就试试 DrissonPage 了，好在 DrissonPage 能过。但究竟 Selenium 和 Playwright 为啥过不去呢，这是个问题。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>有道友说遇到一个 Selenium 自动化过不去的站点 aHR0cHM6Ly93d3cuemhpcGluLmNvbS9qb2JfZGV0YWlsLzgyOTI2ZjM5MDRhNjZlMDUwM1pfMHQtLUZWUlMuaHRtbA==，又有道友说遇到了一个 Playw]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="自动化" scheme="http://program.robinjia.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[一个非常好用的自动化参数]]></title>
    <link href="http://program.robinjia.cc/2025/12/23/%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E5%A5%BD%E7%94%A8%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8C%96%E5%8F%82%E6%95%B0/"/>
    <id>http://program.robinjia.cc/2025/12/23/一个非常好用的自动化参数/</id>
    <published>2025-12-23T12:15:28.000Z</published>
    <updated>2025-12-23T12:17:45.333Z</updated>
    <content type="html"><![CDATA[<p>最近越来越喜欢自动化了，因为在量小的时候，自动化真的太舒服了，轻轻松松就解决了滑块。和道友讨论自动化的时候，道友竟然不知道 Chromium 有 –disable-blink-features=AutomationControlled 这个参数, 于是就写一下这个参数。</p>
<p>我是今年才正经搞了下自动化，问了 AI 怎么隐藏自动化特征，问到了这个参数，AI 真的越来越舒服了。问一下AI这个 –disable-blink-features=AutomationControlled 参数主要干啥的，可以知道这个是 Chromium 启动参数，它的核心作用是：禁用 Blink 渲染引擎中名为 AutomationControlled 的功能特性，从而隐藏浏览器正在被自动化工具（如 Selenium、Puppeteer 等）控制的痕迹，最主要的功能就是 隐藏 navigator.webdriver 属性。</p>
<p>也就是说加上这个参数后，可以解决一些基础检测。这就很舒服了，不用自己去写 hook 解决 navigator.webdriver 检测。</p>
<p>目前测试下来很好用，强的离谱，国内反爬目前还没遇到过不去的，国外倒是挺多过不去的，还需努力。</p>
<p>此时耳边响起嘲讽金角，理解金角，成为金角，超越金角。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>最近越来越喜欢自动化了，因为在量小的时候，自动化真的太舒服了，轻轻松松就解决了滑块。和道友讨论自动化的时候，道友竟然不知道 Chromium 有 –disable-blink-features=AutomationControlled 这个参数, 于是就写一下这个参数。</]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[滑块还是自动化来得舒服]]></title>
    <link href="http://program.robinjia.cc/2025/12/11/%E6%BB%91%E5%9D%97%E8%BF%98%E6%98%AF%E8%87%AA%E5%8A%A8%E5%8C%96%E6%9D%A5%E5%BE%97%E8%88%92%E6%9C%8D/"/>
    <id>http://program.robinjia.cc/2025/12/11/滑块还是自动化来得舒服/</id>
    <published>2025-12-11T01:51:39.000Z</published>
    <updated>2025-12-23T12:17:45.334Z</updated>
    <content type="html"><![CDATA[<p>目标站点 aHR0cHM6Ly9jbG91ZC50ZW5jZW50LmNvbS9wcm9kdWN0L2NhcHRjaGE=</p>
<p>最近遇到一个滑块，打开一看是某讯滑块。看了下加密参数，有 pow_answer 和 collect , 主要难点是 collect，在 vmp 里绕来绕去生成的。本来想用浏览器补环境生成这个参数，但后面想想反正量不大，直接自动化好了。</p>
<p>找同事要了一份滑块自动化的代码，他之前也搞过某讯滑块，只是和这个版本不一样。代码是用Python 的 Playwright 库写的，就着代码让 AI 帮忙修改 XPath, 写等待某个元素出现的代码，让 AI 写等待某个请求出现的代码，最后让用 FastAPI 和 uvicorn 写了个 API 服务，就跑起来了。</p>
<p>量不大的情况下，滑块还是自动化来得舒服。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>目标站点 aHR0cHM6Ly9jbG91ZC50ZW5jZW50LmNvbS9wcm9kdWN0L2NhcHRjaGE=</p>
<p>最近遇到一个滑块，打开一看是某讯滑块。看了下加密参数，有 pow_answer 和 collect , 主要难点是 collect，在 ]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="自动化" scheme="http://program.robinjia.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[再探某Vaptcha手势验证码]]></title>
    <link href="http://program.robinjia.cc/2025/12/10/%E5%86%8D%E6%8E%A2%E6%9F%90Vaptcha%E6%89%8B%E5%8A%BF%E9%AA%8C%E8%AF%81%E7%A0%81/"/>
    <id>http://program.robinjia.cc/2025/12/10/再探某Vaptcha手势验证码/</id>
    <published>2025-12-10T11:12:04.000Z</published>
    <updated>2025-12-23T12:17:45.334Z</updated>
    <content type="html"><![CDATA[<p>目标站点 aHR0cHM6Ly93d3cudmFwdGNoYS5jb20vI2RlbW8=</p>
<p>之前写过一篇<a href="https://mp.weixin.qq.com/s/b6zva3phpw-MpRsKPG_66w" target="_blank" rel="external">某Vaptcha手势验证逆向</a>，能拿到 token, 但真的跑起来，还是会遇到很多问题。</p>
<p>比如能返回 token, 但拿着 token 去请求的时候，会返回参数校验失败。这是因为站点会拿着token去做二次校验，二次校验通不过就会提示参数校验失败，所以能获取到 token 并不代表真的成功。</p>
<p>比如Linux上 Canvas 生成服务容器跑通几次就一直获取不到 token, 本地 Mac 的 Canvas 生成<br>服务<br>容器却没问题。</p>
<p>比如 Windows 机器上的 Canvas 生成服务跑着跑着会变慢，本地 Mac 上的 Canvas 生成服务却一直没问题。</p>
<p>还有就是有的站点 50～55 分钟的时候会跑不动，因为版本发生了变化，得适配。</p>
<p>还有就是轨迹，看着两个轨迹加工的方法没什么大的区别，但真的跑起来效果却相差很大。</p>
<p>最后发现 Canvas 指纹其实随机也能行，不一定要浏览器生成的。但浏览器生成的好的一点是 token 没遇到过 00000000 的情况，随机生成会遇到。</p>
<p>很多时候，还是看请求量。请求量少的时候，怎么方便怎么来，但请求量多的时候，就真的考验人。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>目标站点 aHR0cHM6Ly93d3cudmFwdGNoYS5jb20vI2RlbW8=</p>
<p>之前写过一篇<a href="https://mp.weixin.qq.com/s/b6zva3phpw-MpRsKPG_66w" target="_blank" re]]>
    </summary>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[听闻某数又更新了]]></title>
    <link href="http://program.robinjia.cc/2025/11/23/%E5%90%AC%E9%97%BB%E6%9F%90%E6%95%B0%E5%8F%88%E6%9B%B4%E6%96%B0%E4%BA%86/"/>
    <id>http://program.robinjia.cc/2025/11/23/听闻某数又更新了/</id>
    <published>2025-11-23T08:01:48.000Z</published>
    <updated>2025-12-23T12:17:45.334Z</updated>
    <content type="html"><![CDATA[<p>目标站点: aHR0cHM6Ly9ldGF4LmNoaW5hdGF4Lmdvdi5jbi8=</p>
<p>听道友说某数又更新了，很多补环境方案不能用了，于是测试了下，以前基于 jsdom 的方案果然不能用了。之前就在<a href="https://mp.weixin.qq.com/s/D7hQPPmUJg5cYOil4Q0Zsw" target="_blank" rel="external">当jsdom补环境被针对</a>里写过，如果非得用 jsdom，就只能悄咪咪的用，别声张了。但你得时刻提防着被检测，因为 jsdom 可以检测的点真的是太多了。</p>
<p>继续测试了之前自己搞的补环境方案，还能过，又测试了下今年研究的<a href="https://mp.weixin.qq.com/s/qfJ8gT6IhT6WN-5KBuU1ig" target="_blank" rel="external">浏览器补环境方案</a>，也能过，这就稳妥了，有两个方案在手就还行，不会被打的措手不及。</p>
<p>花了点时间看了下这个版本，毕竟这是22年以来，针对补环境最大的一次更新了。可以看到 rs 安全人员花了很多精力在补环境检测上，几乎把市面上开源的方案都检测过去了, V佬可以的。其中 sdenv 是重灾区。毕竟有了sdenv, 新手都能过了，这可不行。</p>
<p>尝试去找找之前基于 jsdom 的补环境不能通过的原因，发现还是因为检测点太多了，_ast, _runScripts等等。不想继续找了，这补环境错在哪里真难找。还是浏览器补环境来的舒服，啥也不用改，无脑渲染完事。不用去想着怎么把浏览器特性在Node环境里实现，舒服得很。而且这浏览器补环境也就比自己写的补环境方案慢了50%，这性能能接受了。不过需要处理的是，当自动化被检测时，要怎么把这些检测点找出来。至少目前还不需要，目前能过。</p>
<p>遗憾的是，加密算法还是没有太多的改变，估计还是因为改加密算法涉及的东西太多了，阻力太大。如果加密算法大变，再加上这 jsvmp，算法方案就又有得搞了。</p>
<p>不管怎样，等这个版本大规模铺开，人均某数的时代就过去了。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>目标站点: aHR0cHM6Ly9ldGF4LmNoaW5hdGF4Lmdvdi5jbi8=</p>
<p>听道友说某数又更新了，很多补环境方案不能用了，于是测试了下，以前基于 jsdom 的方案果然不能用了。之前就在<a href="https://mp.weixin.q]]>
    </summary>
    
      <category term="jsdom" scheme="http://program.robinjia.cc/tags/jsdom/"/>
    
      <category term="sdenv" scheme="http://program.robinjia.cc/tags/sdenv/"/>
    
      <category term="补环境" scheme="http://program.robinjia.cc/tags/%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[魔改console引发的内存泄漏]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E9%AD%94%E6%94%B9console%E5%BC%95%E5%8F%91%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"/>
    <id>http://program.robinjia.cc/2025/09/21/魔改console引发的内存泄漏/</id>
    <published>2025-09-21T11:36:26.000Z</published>
    <updated>2025-09-22T01:06:27.451Z</updated>
    <content type="html"><![CDATA[<p>在<a href="https://mp.weixin.qq.com/s/9FtnMtRtWNu2SU_8d4QR4Q" target="_blank" rel="external">Node服务内存泄漏问题排查</a>一文中，我们介绍了排查内存泄漏的方法。其实最初我教道友的并不是这个方法，所以道友没有找到内存泄漏原因，后来道友把他的代码发我排查之后，给他找到了解决办法。</p>
<p>后来为了写Node服务内存泄漏问题排查一文，更深入的研究了下这个内存泄漏，才知道主要是看 Retained Size (指的是一个对象被垃圾回收后，能实际释放的内存量)，从上往下找就行，简单易懂。所以还是得多写文章才行啊。</p>
<p>写完Node服务内存泄漏问题排查一文后，用新的方法，一下子就找到了是console内存泄漏了。困惑的是最右边Retained Size对不上，没搞明白。</p>
<p>至于解决的办法也很简单，加上 global.console = undefined 就行了，代码如下</p>
<figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">app.post(<span class="string">"/generateCookies"</span>, <span class="function"><span class="params">(req, res)</span> =&gt;</span> &#123;</span><br><span class="line">    let url = req.body.url;</span><br><span class="line">    let html = req.body.html;</span><br><span class="line">    let cookies = sdk.generateCookies(url, html);</span><br><span class="line">    <span class="built_in">global</span>.<span class="built_in">console</span> = <span class="literal">undefined</span></span><br><span class="line">    res.json(&#123; <span class="attribute">code</span>: <span class="number">0</span>,<span class="string">'data'</span>: &#123;<span class="string">'cookies'</span>: cookies&#125;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>问题是为啥 console 会引发内存泄漏？看了代码后，可以知道它重定义了 console 里的方法，像log, info这些方法。对于这个问题有两种解决办法，一种是把这些重定义代码删掉，如果重定义代码在控制流或者vmp里，就不好找到重定义代码并删除。</p>
<p>另一种是不让它重定义这些方法。而不让它重定义这些方法，就可以用到Object.freeze 在JavaScript逆向时的妙用一文中介绍的 Object.freeze 方法。把console给冻结了，不让它修改，也就不会有内存泄漏。最终代码如下</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Object</span>.freeze(<span class="built_in">console</span>)</span><br><span class="line">app.post(<span class="string">"/generateCookies"</span>, (req, res) =&gt; &#123;</span><br><span class="line">    <span class="keyword">let</span> url = req.body.url;</span><br><span class="line">    <span class="keyword">let</span> html = req.body.html;</span><br><span class="line">    <span class="keyword">let</span> cookies = sdk.generateCookies(url, html);</span><br><span class="line">    res.json(&#123; code: <span class="number">0</span>,<span class="string">'data'</span>: &#123;<span class="string">'cookies'</span>: cookies&#125;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>后来发现，这个禁用console 是JavaScript Obfuscator 里的逻辑，剥离业务代码后，将测试代码放在<a href="https://github.com/dengshilong/js_reverse/tree/main/disable_console" target="_blank" rel="external">https://github.com/dengshilong/js_reverse/tree/main/disable_console</a> 这里，有兴趣的话可以去测试下 console 引发的内存泄漏。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>在<a href="https://mp.weixin.qq.com/s/9FtnMtRtWNu2SU_8d4QR4Q" target="_blank" rel="external">Node服务内存泄漏问题排查</a>一文中，我们介绍了排查内存泄漏的方法。其实最初我教道友]]>
    </summary>
    
      <category term="Node" scheme="http://program.robinjia.cc/tags/Node/"/>
    
      <category term="内存泄漏" scheme="http://program.robinjia.cc/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Node服务内存泄漏问题排查]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/Node%E6%9C%8D%E5%8A%A1%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/"/>
    <id>http://program.robinjia.cc/2025/09/21/Node服务内存泄漏问题排查/</id>
    <published>2025-09-21T11:32:04.000Z</published>
    <updated>2025-09-22T01:06:27.451Z</updated>
    <content type="html"><![CDATA[<p>在使用Express搭建Node服务时，有一个非常值得关注的问题，那就是内存泄漏。内存泄漏会导致服务越来越慢，直至服务崩溃。</p>
<p>最近有个道友反馈，他的服务会内存泄漏，我的服务和他的功能一样，那么也一样会内存泄漏。于是我在Express服务里再加一个接口，用于dump出服务的内存，主要就是使用v8模块的writeHeapSnapshot函数，代码如下。</p>
<figure class="highlight coffeescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">const v8 = <span class="built_in">require</span>(<span class="string">"v8"</span>)</span><br><span class="line">app.get(<span class="string">"/dump"</span>, <span class="function"><span class="params">(req, res)</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        const fileName = v8.writeHeapSnapshot();</span><br><span class="line">        console_log(`<span class="javascript">Heap snapshot written to: $&#123;fileName&#125;</span>`);</span><br><span class="line">        res.status(<span class="number">200</span>).send(`<span class="javascript">Heap snapshot written);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        res.status(<span class="number">500</span>).send(<span class="string">"Internal Server Error"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span></span><br></pre></td></tr></table></figure>
<p>接下来就是请求服务接口，等它内存泄漏之后，调用这个dump内存接口，生成如Heap.20250710.170608.4721.0.001.heapsnapshot 这种文件。之后打开Chrome 开发者调试工具，在Memory里, 导入内存镜像，开始分析内存泄漏原因。</p>
<p>我们可以看到Retained Size(指的是一个对象被垃圾回收后，能实际释放的内存量) 几乎都在global里, 于是重点排查这里。</p>
<p>点开global这里，我们能看到，Retained Size都集中在fetch这个变量，于是我们在代码的最后添加global.fetch = undefined 来释放内存，最终代码如下。之后重启服务，继续测试，内存泄漏问题不再出现， 收工。</p>
<figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">function executeJs(code, cookies, initParam) &#123;</span><br><span class="line">    // 拼接新的 <span class="type">JS</span> 代码</span><br><span class="line">    <span class="keyword">const</span> newHeadJs = headJs + <span class="string">";;;\n"</span> + <span class="string">"window.param="</span> + <span class="type">JSON</span>.stringify(initParam) + <span class="string">";;;;\n"</span> + code;</span><br><span class="line">    // 执行拼接的 <span class="type">JS</span> 代码</span><br><span class="line">    eval(newHeadJs);</span><br><span class="line">    // 构造返回结果</span><br><span class="line">    <span class="keyword">const</span> <span class="literal">result</span> = &#123;</span><br><span class="line">        cookies: utils.changeToCookies(window.document.cookie)</span><br><span class="line">    &#125;;</span><br><span class="line">    // 清除全局 fetch</span><br><span class="line">    global.fetch = undefined;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">result</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
]]></content>
    <summary type="html">
    <![CDATA[<p>在使用Express搭建Node服务时，有一个非常值得关注的问题，那就是内存泄漏。内存泄漏会导致服务越来越慢，直至服务崩溃。</p>
<p>最近有个道友反馈，他的服务会内存泄漏，我的服务和他的功能一样，那么也一样会内存泄漏。于是我在Express服务里再加一个接口，用于du]]>
    </summary>
    
      <category term="Node" scheme="http://program.robinjia.cc/tags/Node/"/>
    
      <category term="内存泄漏" scheme="http://program.robinjia.cc/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[用浏览器渲染代替补环境]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E4%BB%A3%E6%9B%BF%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    <id>http://program.robinjia.cc/2025/09/21/用浏览器渲染代替补环境/</id>
    <published>2025-09-21T11:27:08.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p>在看了土木佬的 <a href="https://mp.weixin.qq.com/s?__biz=MzI1MjE1NjIyMw==&amp;mid=2650862008&amp;idx=1&amp;sn=62c14e5ca3ec959b961af96789dbc682&amp;scene=21&amp;poc_token=HFbhz2ijmmqcVhTPRaW6dZwqC2K75I5A5f0hQAa5" target="_blank" rel="external">某数反爬方案讨论</a> 后，也想搞一个浏览器渲染的方案。之前同事研究cef的时候，就想让他搞一个，但他没继续研究了，这次正好补上。</p>
<p>在 <a href="https://github.com/dengshilong/browser_server/blob/main/node_playwright_server/simple_server.js" target="_blank" rel="external">https://github.com/dengshilong/browser_server/blob/main/node_playwright_server/simple_server.js</a> 代码的基础上，继续问AI，</p>
<p>Playwright的配置参数里，有解决自动化检测的参数吗？AI又哼哧哼哧给了一大堆建议，其中建议有以下一条，加上之后进行测试。</p>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">browser = playwright.chromium.<span class="command">launch</span>(</span><br><span class="line">    args=[</span><br><span class="line">        <span class="string">"--disable-blink-features=AutomationControlled"</span>,  <span class="comment"># 关键参数，隐藏自动化控制标识</span></span><br><span class="line">        <span class="string">"--no-sandbox"</span>,</span><br><span class="line">        <span class="string">"--disable-setuid-sandbox"</span></span><br><span class="line">    ]</span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>会遇到获取cookies时还未生成的情况，于是又让AI加上等待cookies不为空之后再返回结果功能，之后就可以测试了。</p>
<p>先试了加速乐，搞定。再试了下vmp + wasm里生成的某乎__zse_ck，搞定。再试了下某数，也搞定。</p>
<p>浏览器用来补环境是真舒服了, 就是生成速度比纯js补环境慢一些。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p>在看了土木佬的 ]]>
    </summary>
    
      <category term="浏览器" scheme="http://program.robinjia.cc/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
    
      <category term="自动化" scheme="http://program.robinjia.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
      <category term="补环境" scheme="http://program.robinjia.cc/tags/%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[用Playwright和Express写了个浏览器渲染服务]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E7%94%A8Playwright%E5%92%8CExpress%E5%86%99%E4%BA%86%E4%B8%AA%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E6%9C%8D%E5%8A%A1/"/>
    <id>http://program.robinjia.cc/2025/09/21/用Playwright和Express写了个浏览器渲染服务/</id>
    <published>2025-09-21T11:25:12.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p><a href="https://mp.weixin.qq.com/s/mdEWOCv6-JC1PO1i2hSDVw" target="_blank" rel="external">用Playwright和Flask写了个浏览器渲染服务</a>后，测试下来会有内存泄漏的现象，同时也发现它最终是调用 Node 的 Playwright库来实现浏览器渲染。那么我还不如直接用Node 的 Playwright 库来写一个渲染服务，这样应该速度会更快，也可能不会有内存泄漏。</p>
<p>于是又让AI吭哧吭哧的写了一段代码，代码如下</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>);</span><br><span class="line"><span class="keyword">const</span> &#123;</span><br><span class="line">    chromium</span><br><span class="line">&#125; = <span class="built_in">require</span>(<span class="string">'playwright'</span>);</span><br><span class="line"><span class="comment">// 初始化 Express 应用</span></span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line"><span class="keyword">const</span> PORT = process.env.PORT || <span class="number">3000</span>;</span><br><span class="line"><span class="comment">// 中间件</span></span><br><span class="line">app.use(bodyParser.json(&#123;</span><br><span class="line">    limit: <span class="string">'100mb'</span></span><br><span class="line">&#125;));</span><br><span class="line">app.use(bodyParser.urlencoded(&#123;</span><br><span class="line">    limit: <span class="string">'100mb'</span>,</span><br><span class="line">    extended: <span class="literal">true</span></span><br><span class="line">&#125;));</span><br><span class="line"><span class="comment">// 保持一个持久的浏览器实例以提高性能</span></span><br><span class="line"><span class="keyword">let</span> browser;</span><br><span class="line"><span class="comment">// 初始化 playwright 浏览器</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">initializeBrowser</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        browser = <span class="keyword">await</span> chromium.launch(&#123;</span><br><span class="line">            headless: <span class="literal">true</span>,</span><br><span class="line">            args: [</span><br><span class="line">                <span class="string">'--no-sandbox'</span>,</span><br><span class="line">            ]</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'playwright browser initialized successfully'</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(<span class="string">'Failed to initialize playwright browser:'</span>, error);</span><br><span class="line">        <span class="keyword">throw</span> error;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 创建延迟函数 - 强制等待指定毫秒数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">delay</span>(<span class="params">ms</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(resolve =&gt; setTimeout(resolve, ms));</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 将cookies数组转换为字典（键值对）</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">convertCookiesToDict</span>(<span class="params">cookies</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> cookieDict = &#123;&#125;;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(cookies)) &#123;</span><br><span class="line">        cookies.forEach(cookie =&gt; &#123;</span><br><span class="line">            <span class="comment">// 使用cookie的name作为键，value作为值</span></span><br><span class="line">            <span class="keyword">if</span> (cookie.name &amp;&amp; cookie.value !== <span class="literal">undefined</span>) &#123;</span><br><span class="line">                cookieDict[cookie.name] = cookie.value;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> cookieDict;</span><br><span class="line">&#125;</span><br><span class="line">app.post(<span class="string">'/get-cookies'</span>, <span class="keyword">async</span> (req, res) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;</span><br><span class="line">        url,</span><br><span class="line">        html,</span><br><span class="line">        user_agent</span><br><span class="line">    &#125; = req.body;</span><br><span class="line">    <span class="keyword">if</span> (!url || !html) &#123;</span><br><span class="line">        <span class="keyword">return</span> res.status(<span class="number">400</span>).json(&#123;</span><br><span class="line">            error: <span class="string">'请同时提供url和html参数'</span></span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> page;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 配置页面选项，包括可选的userAgent</span></span><br><span class="line">        <span class="keyword">const</span> pageOptions = &#123;&#125;;</span><br><span class="line">        <span class="keyword">if</span> (user_agent) &#123;</span><br><span class="line">            pageOptions.userAgent = user_agent;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">`使用自定义User-Agent: <span class="subst">$&#123;user_agent&#125;</span>`</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 使用配置选项创建新页面</span></span><br><span class="line">        page = <span class="keyword">await</span> browser.newPage(pageOptions);</span><br><span class="line">        <span class="comment">// 设置路由拦截 - 拦截所有请求</span></span><br><span class="line">        <span class="keyword">await</span> page.route(<span class="string">'**/*'</span>, <span class="keyword">async</span> (route, request) =&gt; &#123;</span><br><span class="line">            <span class="comment">// 获取当前请求的URL</span></span><br><span class="line">            <span class="keyword">const</span> requestUrl = request.url();</span><br><span class="line">            <span class="comment">// 检查当前请求是否匹配目标URL</span></span><br><span class="line">            <span class="keyword">if</span> (requestUrl == url) &#123;</span><br><span class="line">                <span class="comment">// 对目标URL返回自定义HTML</span></span><br><span class="line">                <span class="keyword">await</span> route.fulfill(&#123;</span><br><span class="line">                    status: <span class="number">200</span>,</span><br><span class="line">                    content_type: <span class="string">'text/html'</span>,</span><br><span class="line">                    body: html</span><br><span class="line">                &#125;);</span><br><span class="line">                <span class="built_in">console</span>.log(<span class="string">`已处理目标URL请求: <span class="subst">$&#123;requestUrl&#125;</span>`</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 其他所有请求都直接终止</span></span><br><span class="line">                <span class="keyword">await</span> route.abort(<span class="string">'aborted'</span>);</span><br><span class="line">                <span class="built_in">console</span>.log(<span class="string">`已终止非目标URL请求: <span class="subst">$&#123;requestUrl&#125;</span>`</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 导航到目标URL，此时会被我们的路由拦截处理</span></span><br><span class="line">        <span class="keyword">await</span> page.goto(url, &#123;</span><br><span class="line">            timeout: <span class="number">30000</span></span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 获取并返回Cookie</span></span><br><span class="line">        <span class="keyword">let</span> cookies = <span class="keyword">await</span> page.context().cookies();</span><br><span class="line">        cookies = convertCookiesToDict(cookies);</span><br><span class="line">        res.json(&#123;</span><br><span class="line">            success: <span class="literal">true</span>,</span><br><span class="line">            cookies: cookies,</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(<span class="string">'处理请求时出错:'</span>, error);</span><br><span class="line">        res.status(<span class="number">500</span>).json(&#123;</span><br><span class="line">            success: <span class="literal">false</span>,</span><br><span class="line">            error: error.message</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (page) &#123;</span><br><span class="line">            <span class="keyword">await</span> page.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 启动服务器并初始化浏览器</span></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">startServer</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> initializeBrowser();</span><br><span class="line">        app.listen(PORT, () =&gt; &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">`JavaScript execution service running on port <span class="subst">$&#123;PORT&#125;</span>`</span>);</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">`API endpoints:`</span>);</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">`- POST /get-cookies: Execute JavaScript code`</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="built_in">console</span>.error(<span class="string">'Failed to start server:'</span>, error);</span><br><span class="line">        process.exit(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 处理进程退出，确保浏览器正确关闭</span></span><br><span class="line">process.on(<span class="string">'SIGINT'</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'Shutting down...'</span>);</span><br><span class="line">    <span class="keyword">if</span> (browser) &#123;</span><br><span class="line">        <span class="keyword">await</span> browser.close();</span><br><span class="line">    &#125;</span><br><span class="line">    process.exit(<span class="number">0</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 启动服务</span></span><br><span class="line">startServer();</span><br></pre></td></tr></table></figure>
<p>代码都放在Github仓库<a href="https://github.com/dengshilong/browser_server里了，有兴趣的可以看看。" target="_blank" rel="external">https://github.com/dengshilong/browser_server里了，有兴趣的可以看看。</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p><a href=]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="自动化" scheme="http://program.robinjia.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[用Playwright和Flask写了个浏览器渲染服务]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E7%94%A8Playwright%E5%92%8CFlask%E5%86%99%E4%BA%86%E4%B8%AA%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E6%9C%8D%E5%8A%A1/"/>
    <id>http://program.robinjia.cc/2025/09/21/用Playwright和Flask写了个浏览器渲染服务/</id>
    <published>2025-09-21T11:23:48.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！<br>最近对浏览器自动化热情越来越高，于是想用浏览器来取代之前的补环境方案。借助越来越强的AI能力，写写提示词，让它写了一个渲染服务。</p>
<figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> playwright.sync_api <span class="keyword">import</span> sync_playwright</span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> <span class="type">Flask</span>, jsonify, request</span><br><span class="line">app = <span class="type">Flask</span>(__name__)</span><br><span class="line">playwright = sync_playwright().start()</span><br><span class="line">browser = playwright.chromium.launch(</span><br><span class="line">            headless=<span class="type">True</span>,</span><br><span class="line">            args=[</span><br><span class="line">                <span class="string">"--no-sandbox"</span>,</span><br><span class="line">            ])</span><br><span class="line">def generate_cookies(url, html, user_agent=<span class="type">None</span>):</span><br><span class="line">    default_user_agent = '<span class="type">Mozilla</span>/<span class="number">5</span>.<span class="number">0</span> (<span class="type">Windows</span> <span class="type">NT</span> <span class="number">10</span>.<span class="number">0</span>; <span class="type">Win64</span>; x64) <span class="type">AppleWebKit</span>/<span class="number">537</span>.<span class="number">36</span> (<span class="type">KHTML</span>, like <span class="type">Gecko</span>) <span class="type">Chrome</span>/<span class="number">119</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">0</span> <span class="type">Safari</span>/<span class="number">537</span>.<span class="number">36</span>'</span><br><span class="line">    <span class="keyword">if</span> user_agent:</span><br><span class="line">        default_user_agent = user_agent</span><br><span class="line">    context = browser.new_context(</span><br><span class="line">        user_agent=default_user_agent,</span><br><span class="line">    )</span><br><span class="line">    page = context.new_page()</span><br><span class="line">    def handle_request(route, request):</span><br><span class="line">        print('hererer ', request.url, url)</span><br><span class="line">        <span class="keyword">if</span> url <span class="keyword">in</span> request.url:</span><br><span class="line">            <span class="comment"># 返回自定义的 HTML 内容</span></span><br><span class="line">            route.fulfill(status=<span class="number">200</span>, content_type='text/html', body=html)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            route.abort()</span><br><span class="line">    page.route(<span class="string">"**/*"</span>, handler=handle_request)</span><br><span class="line">    page.goto(url)</span><br><span class="line">    <span class="comment"># time.sleep(1)</span></span><br><span class="line">    cookies = context.cookies()</span><br><span class="line">    <span class="literal">result</span> = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> item <span class="keyword">in</span> cookies:</span><br><span class="line">        <span class="literal">result</span>[item['name']] = item['value']</span><br><span class="line">    <span class="comment"># 关闭页面</span></span><br><span class="line">    page.close()</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">result</span></span><br><span class="line">@app.route(<span class="string">"/get-cookies"</span>, methods=[<span class="string">"POST"</span>])</span><br><span class="line">def get_cookies():</span><br><span class="line">    data = request.json</span><br><span class="line">    url = data.get('url', '')</span><br><span class="line">    html = data.get('html', '')</span><br><span class="line">    user_agent = data.get('user_agent', '')</span><br><span class="line">    cookies = generate_cookies(url, html, user_agent=user_agent)</span><br><span class="line">    <span class="literal">result</span> = &#123;&#125;</span><br><span class="line">    <span class="literal">result</span>['cookies'] = cookies</span><br><span class="line">    <span class="keyword">return</span> jsonify(<span class="literal">result</span>)</span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    app.run(host=<span class="string">"0.0.0.0"</span>, threaded=<span class="type">False</span>, processes=<span class="number">1</span>, port=<span class="number">3000</span>, debug=<span class="type">False</span>)</span><br></pre></td></tr></table></figure>
<p>测试代码</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">import requests</span><br><span class="line">headers = &#123;</span><br><span class="line">    'Content-Type': 'application/json',</span><br><span class="line">&#125;</span><br><span class="line">json_data = &#123;</span><br><span class="line">    'url': 'https://www.baidu.com',</span><br><span class="line">    'html': '<span class="tag">&lt;<span class="title">html</span>&gt;</span><span class="tag">&lt;<span class="title">head</span>&gt;</span><span class="tag">&lt;<span class="title">title</span>&gt;</span>Test Page<span class="tag">&lt;/<span class="title">title</span>&gt;</span><span class="tag">&lt;/<span class="title">head</span>&gt;</span><span class="tag">&lt;<span class="title">body</span>&gt;</span><span class="tag">&lt;<span class="title">script</span>&gt;</span><span class="javascript"><span class="built_in">document</span>.cookie = <span class="string">"test=123; path=/"</span>; <span class="built_in">document</span>.cookie = <span class="string">"user=testuser; path=/"</span>;</span><span class="tag">&lt;/<span class="title">script</span>&gt;</span><span class="tag">&lt;/<span class="title">body</span>&gt;</span><span class="tag">&lt;/<span class="title">html</span>&gt;</span>',</span><br><span class="line">&#125;</span><br><span class="line">response = requests.post('http://localhost:3000/get-cookies', headers=headers, json=json_data)</span><br><span class="line">print(response.status_code, response.text)</span><br></pre></td></tr></table></figure>
<p>简简单单，有点意思。当然要运用到生产环境中，还需要解决很多问题，比如内存泄漏，性能等等。<br>代码都放在Github仓库<a href="https://github.com/dengshilong/browser_server里了，有兴趣的可以看看。" target="_blank" rel="external">https://github.com/dengshilong/browser_server里了，有兴趣的可以看看。</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！<br>最近对浏览器自动化热情越]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="自动化" scheme="http://program.robinjia.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[数据清洗真的太重要了]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97%E7%9C%9F%E7%9A%84%E5%A4%AA%E9%87%8D%E8%A6%81%E4%BA%86/"/>
    <id>http://program.robinjia.cc/2025/09/21/数据清洗真的太重要了/</id>
    <published>2025-09-21T11:23:06.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>前段时间，为了排查问题，在Hive表里各种查询，最后发现数据清洗真是太重要了，数据不给你清洗好，爬虫采集的再好也是白搭。</p>
<p>我们知道在数据仓库体系中一般有ODS、DWD、DWS 和 ADS 四层。数据清洗的时候，会先把爬虫采集的数据先存到ODS，之后经过处理到DWD，DWS，ADS，这其中每一步出问题都有可能导致数据丢失。一般情况，存入ODS不会出问题，因为这一步没有做什么特别操作，就把数据从HDFS文件写到ODS表中。</p>
<p>清洗容易挖坑的情况是表设计不好，比如ODS里会有来自很多个地方的数据，不给你加上数据来源字段，排查问题就很麻烦。比如不记录数据采集时间，也会造成排查问题麻烦。</p>
<p>更坑的是ODS到DWD，DWD到DWS或者DWS到ADS，因为清洗规则写的太复杂，把数据给清洗丢了，可能丢的不多，但长时间累积就很多了，正常应该保证一条都不丢。</p>
<p>还有一些很细的清洗情况。比如一个数值字段，之前都是正常阿拉伯计数，如1400000，后面突然改成科学计数法，如1.4E6，如果刚开始清洗时没考虑到这个情况，会导致这个字段清洗出错，此时得加一些告警才能发现问题。</p>
<p>还有一些情况是一条记录的发布时间字段，刚开始是有的，后面发布时间字段又变成空了，此时应该保留原来的发布时间。</p>
<p>数据清洗是个精细活，得慢慢来。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>前段时间，为了排查问题，在Hive表里各种查询，最后发现数据清洗真是太重要了，数据不给你清洗好，爬虫采集的再好也是白搭。</p>
<p>我们知道在数据仓库体系中一般有ODS、DWD、DWS 和 ADS 四层。数据清洗的时候，会先把爬虫采集的数据先存到ODS，之后经过处理到D]]>
    </summary>
    
      <category term="数据清洗" scheme="http://program.robinjia.cc/tags/%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[自动化还是得好好学一下]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%98%E6%98%AF%E5%BE%97%E5%A5%BD%E5%A5%BD%E5%AD%A6%E4%B8%80%E4%B8%8B/"/>
    <id>http://program.robinjia.cc/2025/09/21/自动化还是得好好学一下/</id>
    <published>2025-09-21T11:21:56.000Z</published>
    <updated>2025-09-21T11:30:00.241Z</updated>
    <content type="html"><![CDATA[<p>有段时间要处理验证码，要训练验证码得先把验证码图片下载下来，但是下载图片有加密参数，加密接口还没有逆向，不知道得搞多久。于是就学习怎么使用Selenium, 对着文档写好了一个下载脚本，不用管任何加密参数，又快又稳，舒舒服服。</p>
<p>有个网站需要注册账号，逆向注册接口一直不知道错在哪里，后来用自动化DrissionPage写了个注册账号的脚本，跑的稳稳的。</p>
<p>之前遇到滑块，看加密参数，搞了好久搞不定，结果用自动化，搞一下轨迹就过了。</p>
<p>刚入行的时候，看不上自动化，因为速度慢，但在一些量小的场景下，自动化确实是又快又稳的方案。要知道技术只是手段，业务方可不会管你是怎么做爬虫的，只要你能满足他的需求就行。自动化，补环境，扣算法都只是技术手段，在满足需求的情况下，没有高低之分。</p>
<p>此时想起了爬虫行业金句，嘲讽金角，理解金角，成为金角，超越金角。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>有段时间要处理验证码，要训练验证码得先把验证码图片下载下来，但是下载图片有加密参数，加密接口还没有逆向，不知道得搞多久。于是就学习怎么使用Selenium, 对着文档写好了一个下载脚本，不用管任何加密参数，又快又稳，舒舒服服。</p>
<p>有个网站需要注册账号，逆向注册接]]>
    </summary>
    
      <category term="爬虫" scheme="http://program.robinjia.cc/tags/%E7%88%AC%E8%99%AB/"/>
    
      <category term="自动化" scheme="http://program.robinjia.cc/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[解决disable-devtool控制台反调试]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E8%A7%A3%E5%86%B3disable-devtool%E6%8E%A7%E5%88%B6%E5%8F%B0%E5%8F%8D%E8%B0%83%E8%AF%95/"/>
    <id>http://program.robinjia.cc/2025/09/21/解决disable-devtool控制台反调试/</id>
    <published>2025-09-21T11:19:45.000Z</published>
    <updated>2025-09-21T11:30:00.241Z</updated>
    <content type="html"><![CDATA[<p>从业以来第一次遇到站点无法使用快捷键打开开发者工具，猜测是监听了键盘事件，于是只好在浏览器设置里点击打开</p>
<p>发现打开开发者工具后，会跳转about:blank空白页面，于是把网页代码下载到本地，之后在代码里搜索about:blank, 发现如下可疑代码</p>
<p>把window.location.href=”about:blank”删除之后，用Charles等抓包工具里的Map Local(映射本地文件)进行文件替换。此时还是无法使用快捷键打开开发者工具，但依然通过浏览器设置打开开发者工具，好在页面不再跳转空白页。</p>
<p>此时console日志一直在打印，跳到日志输出的代码文件，大概看了下反调试逻辑，用了很多方案，窗口大小，时间等等。在代码文件里找到一个链接 <a href="https://theajack.github.io/disable-devtool/404.html，于是我们知道这个反调试是用的https://github.com/theajack/disable-devtool" target="_blank" rel="external">https://theajack.github.io/disable-devtool/404.html，于是我们知道这个反调试是用的https://github.com/theajack/disable-devtool</a> 这个库。</p>
<p>查看这个库的源码，我们可以知道它是通过监听keydown事件，屏蔽了打开开发者工具的快捷键。代码片段如下</p>
<figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">target.addEventListener('keydown', (<span class="keyword">e</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">e</span> = <span class="keyword">e</span> || target.event;</span><br><span class="line">  <span class="keyword">const</span> keyCode = <span class="keyword">e</span>.keyCode || <span class="keyword">e</span>.<span class="keyword">which</span>;</span><br><span class="line">  <span class="keyword">if</span> (</span><br><span class="line">    keyCode === KEY.F12 || <span class="comment">// 禁用f12</span></span><br><span class="line">    isOpenDevToolKey(<span class="keyword">e</span>, keyCode) || <span class="comment">// 禁用 ctrl + shift + i</span></span><br><span class="line">    isViewSourceCodeKey(<span class="keyword">e</span>, keyCode) <span class="comment">// 禁用 ctrl + u 和 ctrl + s 查看和保存源码</span></span><br><span class="line">  ) &#123;</span><br><span class="line">    <span class="keyword">return</span> preventEvent(target, <span class="keyword">e</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;, true);</span><br></pre></td></tr></table></figure>
<p>之后看了下初始化代码，找到绕过的办法。只要不让它执行initInterval，disableKeyAndMenu，initDetectors这几个函数即可，直接设置disableDevtool.isRunning = true即可。 网站代码经过混淆，但没有高度混淆，就webpack打包了下，在对应的代码里找到isRunning变量，把它设置为true即可。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="built_in">disable</span>Devtool.isRunning) <span class="built_in">return</span> r(<span class="string">'already running'</span>);</span><br><span class="line">initIS(); // ! 首先初始化env</span><br><span class="line">initLogs(); // 然后初始化<span class="built_in">log</span></span><br><span class="line">mergeConfig(opts);</span><br><span class="line">// 被 token 绕过 或者</span><br><span class="line"><span class="keyword">if</span> (checkTk()) <span class="built_in">return</span> r(<span class="string">'token passed'</span>);</span><br><span class="line">// 开启了保护seo 并且 是seobot</span><br><span class="line"><span class="keyword">if</span> ((config.seo &amp;&amp; IS.seoBot)) <span class="built_in">return</span> r(<span class="string">'seobot'</span>);</span><br><span class="line"><span class="built_in">disable</span>Devtool.isRunning = <span class="literal">true</span>;</span><br><span class="line">initInterval(<span class="built_in">disable</span>Devtool);</span><br><span class="line"><span class="built_in">disable</span>KeyAndMenu(<span class="built_in">disable</span>Devtool);</span><br><span class="line">initDetectors();</span><br><span class="line"><span class="built_in">return</span> r();</span><br></pre></td></tr></table></figure>
<p>最后发现disable-devtool这个库挺多star的, 作者是国内开发者，是个精力旺盛的开发，作者还给了一个测试网站</p>
<p><a href="https://theajack.github.io/disable-devtool/，有兴趣的可以试试。" target="_blank" rel="external">https://theajack.github.io/disable-devtool/，有兴趣的可以试试。</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>从业以来第一次遇到站点无法使用快捷键打开开发者工具，猜测是监听了键盘事件，于是只好在浏览器设置里点击打开</p>
<p>发现打开开发者工具后，会跳转about:blank空白页面，于是把网页代码下载到本地，之后在代码里搜索about:blank, 发现如下可疑代码</p>
]]>
    </summary>
    
      <category term="disable-devtool" scheme="http://program.robinjia.cc/tags/disable-devtool/"/>
    
      <category term="反调试" scheme="http://program.robinjia.cc/tags/%E5%8F%8D%E8%B0%83%E8%AF%95/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[又收集到一个开源某数补环境方案]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E5%8F%88%E6%94%B6%E9%9B%86%E5%88%B0%E4%B8%80%E4%B8%AA%E5%BC%80%E6%BA%90%E6%9F%90%E6%95%B0%E8%A1%A5%E7%8E%AF%E5%A2%83%E6%96%B9%E6%A1%88/"/>
    <id>http://program.robinjia.cc/2025/09/21/又收集到一个开源某数补环境方案/</id>
    <published>2025-09-21T11:17:29.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p>之前就知道这个 <a href="https://www.52pojie.cn/forum.php?mod=viewthread&amp;tid=2012413，但一直没去试。正好周末闲着没事，于是研究了下这个补环境方案。" target="_blank" rel="external">https://www.52pojie.cn/forum.php?mod=viewthread&amp;tid=2012413，但一直没去试。正好周末闲着没事，于是研究了下这个补环境方案。</a></p>
<p>这个方案没有加 <a href="https://mp.weixin.qq.com/s/3NeI6AendlTlNMIrKL0G8Q" target="_blank" rel="external">window.ActiveXObject=undefined</a> 这个代码，也就没有走IE那一套，而是直接按照 Chrome 硬补，form 表单校验，document.all 校验这些都给补了，这就很好。而它没有用vm2 创建沙盒环境，直接在 Node 上补，这就减少了创建沙盒环境的时间，在速度上也就有了保证。</p>
<p>虽然这个环境没有严格的 DOM 树操作，但它的确能跑通，而能跑通对于绝大多数爬虫来说就足够了。</p>
<p>将它进行了改造，改成Express服务，发现还是挺通用的，测试了几个普通站点能过，但校验严格的站点还是过不去。知足了，毕竟免费的，还想怎样。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p>之前就知道这个 ]]>
    </summary>
    
      <category term="补环境" scheme="http://program.robinjia.cc/tags/%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[如何定制sdenv补环境]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E5%A6%82%E4%BD%95%E5%AE%9A%E5%88%B6sdenv%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    <id>http://program.robinjia.cc/2025/09/21/如何定制sdenv补环境/</id>
    <published>2025-09-21T11:13:52.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>在 <a href="https://mp.weixin.qq.com/s/QJ9X1O1EV6g22CAAhacaPA" target="_blank" rel="external">某数反爬方案调研</a> 和 <a href="https://mp.weixin.qq.com/s/vY_Y9AzEJ41K4utt-uKaYg" target="_blank" rel="external">听闻sdenv被反爬</a> 里，我们都提到了sdenv, 它就是一个在 jsdom 上魔改的某数补环境方案。而在听闻sdenv被反爬里，也提到过它的很多环境设置还是有问题，那么如果要魔改sdenv又要如何操作呢？有道友问了这个问题，这里记录一下。</p>
<p>sdenv 执行的时候有一行代码很关键，那就是 browser(window, ‘chrome’) 这行，加上这行它会去加载一个写好的 chrome 环境代码，而这些代码就在browser/chrome目录里。所以我们要定制sdenv, 也可以在这里增加代码。</p>
<p>举个简单例子来说，sdenv 在执行window.document.toString()时返回的结果是[object Document]而不是 [object HTMLDocument]，有什么办法修改它的这个toString吗？其实只要修改browser/chrome目录里的document.js文件就好, 给它加上如下两行代码，之后window.document.toString() 就会发生变化了。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.document.toString = <span class="function"><span class="keyword">function</span> <span class="title">toString</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">'[object HTMLDocument]'</span></span><br><span class="line">&#125;</span><br><span class="line">sdenv.tools.setFuncNative(<span class="built_in">window</span>.document.toString)</span><br></pre></td></tr></table></figure>
]]></content>
    <summary type="html">
    <![CDATA[<p>在 <a href="https://mp.weixin.qq.com/s/QJ9X1O1EV6g22CAAhacaPA" target="_blank" rel="external">某数反爬方案调研</a> 和 <a href="https://mp.weixin.qq]]>
    </summary>
    
      <category term="JavaScript" scheme="http://program.robinjia.cc/tags/JavaScript/"/>
    
      <category term="jsdom" scheme="http://program.robinjia.cc/tags/jsdom/"/>
    
      <category term="某数" scheme="http://program.robinjia.cc/tags/%E6%9F%90%E6%95%B0/"/>
    
      <category term="补环境" scheme="http://program.robinjia.cc/tags/%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Object.freeze在JavaScript逆向时的妙用]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/Object-freeze%E5%9C%A8JavaScript%E9%80%86%E5%90%91%E6%97%B6%E7%9A%84%E5%A6%99%E7%94%A8/"/>
    <id>http://program.robinjia.cc/2025/09/21/Object-freeze在JavaScript逆向时的妙用/</id>
    <published>2025-09-21T11:10:55.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>在 JavaScript 逆向时，有时候反爬代码会对console进行更改，导致使用 console.log 输出时没有结果，如下面的例子</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log = <span class="function"><span class="keyword">function</span> <span class="title">log</span>(<span class="params">t</span>) </span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'ssss'</span>)</span><br></pre></td></tr></table></figure>
<p>这时我们可以使用 Object.freeze 方法把 console 冻结了，不让它更改，这样就会有输出了，例子如下</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Object</span>.freeze(<span class="built_in">console</span>)</span><br><span class="line"><span class="built_in">console</span>.log = <span class="function"><span class="keyword">function</span> <span class="title">log</span>(<span class="params">t</span>) </span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'ssss'</span>)</span><br></pre></td></tr></table></figure>
<p>在MDN文档里可以看到，Object.freeze() 静态方法可以使一个对象被冻结。冻结对象可以防止扩展，并使现有的属性不可写入和不可配置。被冻结的对象不能再被更改：不能添加新的属性，不能移除现有的属性，不能更改它们的可枚举性、可配置性、可写性或值，对象的原型也不能被重新指定。freeze() 返回与传入的对象相同的对象。</p>
<p>当然反爬也可以使用 Object.isFrozen() 方法查看 console 是否被冻结, 发现被冻结了，就知道大概率是爬虫了，因为正常用户谁会好端端的冻结 console 呢？</p>
<h3 id="u53C2_u8003_u8D44_u6599_3A"><a href="#u53C2_u8003_u8D44_u6599_3A" class="headerlink" title="参考资料:"></a>参考资料:</h3><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze" target="_blank" rel="external">https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>在 JavaScript 逆向时，有时候反爬代码会对console进行更改，导致使用 console.log 输出时没有结果，如下面的例子</p>
<figure class="highlight javascript"><table><tr><td class="gutt]]>
    </summary>
    
      <category term="JavaScript" scheme="http://program.robinjia.cc/tags/JavaScript/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[测试了下盾山加密补环境]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E6%B5%8B%E8%AF%95%E4%BA%86%E4%B8%8B%E7%9B%BE%E5%B1%B1%E5%8A%A0%E5%AF%86%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    <id>http://program.robinjia.cc/2025/09/21/测试了下盾山加密补环境/</id>
    <published>2025-09-21T11:06:58.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p>目标站点: aHR0cHM6Ly9kcHB0LmJlaWppbmcuY2hpbmF0YXguZ292LmNuOjg0NDMvZGlnaXRhbC10YXgtYWNjb3VudA==， 目标参数为请求头中的 lzkqow23819 参数和 请求url 中 6eMrZlPH 参数。</p>
<p>很早之前就听过盾山加密，说是要逆向出算法很有挑战，补环境则相对简单些，于是一直没有尝试。今年抽空试了下补环境，以备不时之需。</p>
<p>之前因为在补环境框架跑的太慢了，于是好久没搞补环境框架了，现在都直接用Node 补了。补的过程中就会发现这个补环境确实相对容易，没有各种花里胡哨的检测，就缺啥补啥完事。唯一需要注意的一点是 typeof 检测，typeof 没法用代理捕获到，得看代码分析了才知道。这个反爬缺啥补啥后就能直接跑起来，没有一点坏心思。</p>
<p>在<a href="https://mp.weixin.qq.com/s/vk6e14QaqMEJ6FUKQnUKLg" target="_blank" rel="external">我对反爬的一些理解</a>有提到过，补环境是可以无视混淆的，也不管算法。算法加密再厉害，混淆的再花里胡哨，反爬代码对环境的检测强度不够，一样是无效的。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>声明: 本文章中所有内容仅供学习交流使用，不用于其他任何目的，不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关！若有侵权，请在公众号 【静夜随想】 联系作者立即删除！</p>
<p>目标站点: aH]]>
    </summary>
    
      <category term="盾山" scheme="http://program.robinjia.cc/tags/%E7%9B%BE%E5%B1%B1/"/>
    
      <category term="补环境" scheme="http://program.robinjia.cc/tags/%E8%A1%A5%E7%8E%AF%E5%A2%83/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[第一次遇到uniapp逆向]]></title>
    <link href="http://program.robinjia.cc/2025/09/21/%E7%AC%AC%E4%B8%80%E6%AC%A1%E9%81%87%E5%88%B0uniapp%E9%80%86%E5%90%91/"/>
    <id>http://program.robinjia.cc/2025/09/21/第一次遇到uniapp逆向/</id>
    <published>2025-09-21T11:04:47.000Z</published>
    <updated>2025-09-21T11:30:00.240Z</updated>
    <content type="html"><![CDATA[<p>道友遇到一个无壳APP，有个加密token不知道怎么生成的，jadx反编译之后也搜索不到参数，于是请教我怎么搞。猜测有可能是在JavaScript里，因为之前遇到过一个Weex开发的APP，也是怎么搜索都找不到加密参数，最后在JavaScript里找到了。</p>
<p>那次Weex开发的APP，找了老半天都不知道咋回事，后面是通过hook string还是hook url的办法，发现它会去下载一个JavaScript脚本，也就是 app-service.js，在这个脚本里才找到了加密参数。</p>
<p>于是道友就去找JavaScript代码，在反编译的apk里的assets目录里，找到了一个 uniapp 开发的app-service.js，在代码里找到了相关接口，但还是找不到相关的加密参数。于是道友又困惑了，只好来请教我。</p>
<p>此时我才知道这是使用 uniapp 开发的APP，于是去应用宝里下载了一个apk, 安装后发现它提示APP需要更新，点击更新后，APP又去下载了一些东西。拿着apk 里的 app-service.js 看了之后，接口确实都有，怎么会没有参数呢？想到刚才 APP 提示更新，莫非是会更新了uniapp? 于是去找APP临时目录，在/data/data目录下找到 APP对应的目录，在里面找到了一个app-service.js, 和apk里的app-service.js 大小不一样，搜索加密参数，果然在这里。剩下的就交给道友去解决了。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>道友遇到一个无壳APP，有个加密token不知道怎么生成的，jadx反编译之后也搜索不到参数，于是请教我怎么搞。猜测有可能是在JavaScript里，因为之前遇到过一个Weex开发的APP，也是怎么搜索都找不到加密参数，最后在JavaScript里找到了。</p>
<p>那]]>
    </summary>
    
      <category term="APP" scheme="http://program.robinjia.cc/tags/APP/"/>
    
      <category term="JavaScript" scheme="http://program.robinjia.cc/tags/JavaScript/"/>
    
      <category term="uniapp" scheme="http://program.robinjia.cc/tags/uniapp/"/>
    
      <category term="逆向" scheme="http://program.robinjia.cc/tags/%E9%80%86%E5%90%91/"/>
    
  </entry>
  
</feed>
