(或至少让其更难抓取)
这篇文章我翻译自:https://github.com/JonasCz/How-To-Prevent-Scraping,因为最近在看一些反爬的资料,无意间在 Github 发现这篇文章,我觉得写的很全面,所以想要翻译一下,顺便进行吸收。另外让我觉得非常赞的是这个文章从服务端和前端的角度都做了分析,我们应该从哪些点去做优化,而且每个点都举了例子,有些还给出了注意点。虽然文中有些提到的一些点很基础,也有很多我们目前在业务中也在使用,但是我还是从中吸收到了一些新东西,里面文中外部链接有很多,但是公众号这里不允许外链,所以大家可以点击文末最后的"阅读原文"到 Github 去查看,Github Markdown 的排版可能也比这里更好 : )。
(最后,有些仓促,可能有些注意不到的措别字,还请多多包涵)
提示:这篇文章是我 Stack Overflow 这个问题回答的扩展,我把它整理在 Github 因为它实在是太长了,超过了 Stack Overflow 的字数限制(最多 3 万个字,这文章已经超过 4 万字)
欢迎大家修改、完善还有分享,本文使用 CC-BY-SA 3.0 许可。
本质上说,防抓的目的在于增加脚本或机器获取你网站内容的难度,而不要影响真实用户的使用或搜索引擎的收录
不幸的是这挺难的,你需要在防抓和降低真实用户以及搜索引擎的可访问性之间做一下权衡。
为了防爬(也称为网页抓取、屏幕抓取、网站数据挖掘、网站收割或者网站数据获取),了解他们的工作原理很重要,这能防止他们能高效爬取,这个就是这篇文章的主要内容。
通常情况下,抓取程序的目的是为了获取你网站特定的信息,比如文章内容、搜索结果、产品详情还有网站上艺术家或者相册信息。他们爬取这些内容用于维护他们自己的网站(甚至通过你的内容赚钱!),或者制作和你网站不一样的前端界面(比如去做移动 APP),还有一些可能作为个人研究或分析使用。
实际上,有特别多的爬虫类型,而且他们的爬取方式都不太相同:
举个例子:如果你的网站有搜索功能,那这个爬虫可能会模拟提交一个搜索的 HTTP 请求,然后从搜索结果页中获取所有的链接和标题,有时候会构造有成千上百不同的请求,只是为了获取标题和链接而已。这是最常见的一类爬虫。
基于浏览器的爬取很难处理,他们运行脚本,渲染 HTML,就像真实用户一样访问你的网站。
毫无疑问,防止专业的爬取服务是非常困难的,但是如果你增加爬取的难度,或者增加他们找出爬取方法的时间,那这些人(或者付钱让他们做的人)可能会放弃爬取你的网站。
虽然没有什么技术含量,但是确实也是个问题,比如移动 APP(Android 和 iOS)可以嵌入你的网站,甚至可以注入一些自定义的 CSS 和 JavaScript,所以可以完全改变你网站的外观,然后只展示想要的信息,比如只展示文章内容或者搜索结果,然后隐藏你网站的 headers、footers 还有广告。
以上不同的爬虫类型有很多相同点,很多爬虫的行为也很相似,即使他们使用了不同的技术或者方案去爬取你的内容。
这些大多数都是我自己的想法,我在写爬虫时也遇到许多困难,还有一些是来源于网络。
一些常见的检测和防止爬虫的方法:
周期性的检查你的日志,如果发现有异常活动表明是自动爬取(爬虫),类似某一个相同的 IP 很多相同的行为,那你就可以阻止或限制访问。
一些常用的方法:
只允许用户(或爬虫)在一段时间内访问特定的次数,举个例子,某个 IP 或用户只允许一分钟搜索很少的次数。这个会减慢爬虫的爬取速度,让他们变得低效。如果次数特别多或者比真实用户多很多,那你也可以显示验证码页面。
如果你能看到异常行为,比如同一个 IP 有很多相同的请求,有些会翻很多页或者访问一些异常的页码,你可以拒绝访问或者在后续的请求中展示验证码。
如果你做了访问限制或频率限制,不要只简单的根据单个 IP 地址去做;你可以通过其他的标识和方法去识别一个用户或爬虫。一些可以帮你识别用户/爬虫的标识:
举个例子,如果某个 IP 请求了你网站很多次,所有的访问都有相同的 UserAgent 、屏幕尺寸(JavaScript 检测),还有全都使用同样的方式和固定的时间间隔点击同一个按钮,那它大概是一个屏幕爬虫;你可以临时限制相似的请求(比如 只限制来自那个 IP 特定 User-Agent 和 屏幕尺寸的请求),这样你不会误伤同样使用这个 IP 的真实用户,比如共享网络链接的用户。
更进一步,当你发现相似的请求,但是来自不同的 IP 地址,表明是分布式爬虫(使用僵尸网络或网络代理的爬虫)。如果你收到类似大量的请求,但是他们来自不同的 IP 地址,你可以拒绝访问。再强调一下,小心不经意限制了真实用户。
这种方法对那种运行 JavaScript 的屏幕爬虫比较有效,因为你可以获得他们大量的信息。
Stack Exchange 上关于 Secruity 的相关问题:
How to uniquely identify users with the same external IP address?
Why do people use IP address bans when IP addresses often change? 关于仅使用 IP 的其他限制
对于频率限制,最简单的方式就是临时限制访问,然而使用验证码会更好,看下面关于验证码的部分。
如果可行,要求创建用户才可以查看你的内容。这个可以很好的遏制爬虫,但很容易遏制真实用户:
为了防止爬虫创建大量的用户,你应该:
要求注册用户对用户和搜索引擎来说不友好;如果你要求必须注册才可以看文章,那用户也可能直接离开。
有时爬虫会被运行在云服务商,比如 Amazon Web Services 或 Google App Engine,或者其他 VPS。限制这些来自云服务商的 IP 地址访问你的网站(或出验证码)。你可以可以直接对来自爬取服务的 IP 地址限制访问。
类似的,你也可以限制来自代理或 VPN 的 IP 地址,因为很多爬虫会使用它们来防止被检测到。
但是要知道限制来自代理服务器或 VPN 的 IP,也很容易影响到真实用户。
如果你要封禁或限制访问,你应该确保不要把导致封禁的原因告诉爬虫,他们会根据提示修改自己的爬虫程序。所以下面的这些错误最好不要出现:
想法的,展示一些不包含原因的友好信息会更好,比如下面的信息会更好:
如果真实用户看到这个错误页面,这个对真实用户来说也十分友好。在后续访问中,你也可以考虑展示验证码而不是直接封禁,如果真实用户看到这个错误信息,对于合法用户也会联系你。
验证码(Captcha,“Completely Automated Test to Tell Computers and Humans Apart”)对于防爬十分有效。不幸的是,它也很容易惹恼用户。
因此,如果你发现疑似爬虫,而且想要阻止它的爬取行为,而不是封禁它以免它是真实用户。你可以考虑显示验证码在你再次允许访问之前。
使用验证码的注意事项:
你可以在服务端将文字渲染成图片显示,他将防止简单的爬虫去获取文字内容。
然而,这个对于屏幕阅读器、搜索引擎、性能还有一些其他一些事情都不太好。这个在某些方面也是不违法的(源于访问性问题,eg. the Americans with Disabilities Act),而且对于一些 OCR 爬虫也能非常简单的规避,所以不要采用这种方式。
你也可以用 CSS sprites 做类似的事情,但是也有相同的问题。
如果可以的话,不要让脚本/爬虫能获取你所有的数据。举个例子:你有一个新闻站,有大量的个人文章。你应该确保文章只能通过你自己网站的搜索到,如果你网站不是到处都有你的文章还有链接,那么确保它们只能被搜索功能访问到。这意味着如果一个脚本想要获取你网站的所有文章,就必须通过你站点文章中所有可能的短语去进行搜索,从而获取所有的文章列表,但是这个会非常耗时,而且低效,从而让爬虫放弃。
以下操作会让你完全暴露:
example.com/article.php?articleId=12345
,这样做(或类似的其他做法)会让爬虫很简单的迭代 articleID
就能获取你所有的文章确保你不会暴露你的任何 API,很多时候都是无意间暴露。举个例子,如果你正在使用 AJAX 或 Adobe Flash 的网络请求 或 Java Applet(千万不要用!)来加载你的数据,从这些网络请求中找到要请求的 API 十分简单,比如可以进行反编译然后在爬虫程序中使用这些接口。确保你混淆了你的 API 并且其他人要用它会非常难破解。
由于 HTML 解析器是通过分析页面特定结构去获取内容,我们可以故意修改这些结构来阻止这类爬虫,甚至从根本上他们获取内容。下面大多数做法对其他类型爬虫如搜索引擎蜘蛛、屏幕爬虫也有效。
处理 HTML 的爬虫通过分析特定可识别的部分来处理 HTML。举个例子:如果你所有的页面都有 id 为 article-content
的 div
结构,然后里面有你的文章内容,那要获取你站点所有文章内容是十分简单的,只要解析那个 article-content
那个 div
就可以了,然后有这个结构的爬虫就可以把你的文章随便用在什么地方。
如果你常常修改 HTML 还有你页面的结构, 那这类爬虫就不能一直使用。
id
和 class
,甚至让他能自动改变。所以,如果你的 div.article-content
变成 div.a4c36dda13eaf0
,而且每周都会变化,那么爬虫一开始可能能正常工作,但是一周之后就不能使用了。同时也确保修改你 id/class的长度,这样也可以避免爬虫使用类似 div.[any-14-characters]
来找到想要的 div
。div
,并且里面通过 h1
放文章的标题,爬虫将基于这个结构获取文章内容。同样的,为了防止这个,你可以在你的 HTML 添加/删除 额外的标记,周期并且随机的做,eg. 添加额外的 div
或 span
。对于服务端渲染的程序,这应该不会很难。注意事项:
本质上来说,就是确保爬虫从相似页面获取想要的内容变得不那么容易。
可以参考这个 PHP 的实现:How to prevent crawlers depending on XPath from getting page contents
这个和前一个类似。如果你根据不同用户的位置/国家(根据 IP 获取)来提供不同的 HTML,这个可能会破坏将站点 HTML 给用户的爬虫。比如,如果有人写了一个移动 APP 来抓取你的站点,一开始可以用,但是对于不同地区的用户就不起作用了,因为他们会获取到不同的 HTML 结构,嵌入式的 HTML 将不不能正常使用。
举个例子:你有一个包含搜索功能的网站, example.com/search?query=somesearchquery
的搜索结果是下面的 HTML 结构:
1 |
<span class="code-snippet_outer"><span class="code-snippet__tag"><<span class="code-snippet__name">div</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"search-result"</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">h3</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"search-result-title"</span>></span>Stack Overflow has become the world's most popular programming Q & A website<span class="code-snippet__tag"></<span class="code-snippet__name">h3</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">p</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"search-result-excerpt"</span>></span>The website Stack Overflow has now become the most popular programming Q & A website, with 10 million questions and many users, which...<span class="code-snippet__tag"></<span class="code-snippet__name">p</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">a</span> <span class="code-snippet__attr">class</span>"<span class="code-snippet__attr">search-result-link</span>" <span class="code-snippet__attr">href</span>=<span class="code-snippet__string">"/stories/stack-overflow-has-become-the-most-popular"</span>></span>Read more<span class="code-snippet__tag"></<span class="code-snippet__name">a</span>></span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__tag"></<span class="code-snippet__name">div</span>></span></span></code><code><span class="code-snippet_outer">(And so on, lots more identically structured divs with search results)</span> |
你可以猜到这个非常容易爬取:一个爬虫需要做的就只是访问搜索链接,然后从返回的 HTML 分析想要的数据。除了定期修改上面 HTML 的内容,你也可以保留旧结构的 id 和 class,然后使用 CSS 进行隐藏,并使用假数据进行填充,从而给爬虫投毒。比如像下面这样:
1 2 |
<span class="code-snippet_outer"><span class="code-snippet__tag"><<span class="code-snippet__name">div</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"the-real-search-result"</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">h3</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"the-real-search-result-title"</span>></span>Stack Overflow has become the world's most popular programming Q & A website<span class="code-snippet__tag"></<span class="code-snippet__name">h3</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">p</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"the-real-search-result-excerpt"</span>></span>The website Stack Overflow has now become the most popular programming Q & A website, with 10 million questions and many users, which...<span class="code-snippet__tag"></<span class="code-snippet__name">p</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">a</span> <span class="code-snippet__attr">class</span>"<span class="code-snippet__attr">the-real-search-result-link</span>" <span class="code-snippet__attr">href</span>=<span class="code-snippet__string">"/stories/stack-overflow-has-become-the-most-popular"</span>></span>Read more<span class="code-snippet__tag"></<span class="code-snippet__name">a</span>></span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__tag"></<span class="code-snippet__name">div</span>></span></span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"><span class="code-snippet__tag"><<span class="code-snippet__name">div</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"search-result"</span> <span class="code-snippet__attr">style</span>=<span class="code-snippet__string">"display:none"</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">h3</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"search-result-title"</span>></span>Visit example.com now, for all the latest Stack Overflow related news !<span class="code-snippet__tag"></<span class="code-snippet__name">h3</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">p</span> <span class="code-snippet__attr">class</span>=<span class="code-snippet__string">"search-result-excerpt"</span>></span>EXAMPLE.COM IS SO AWESOME, VISIT NOW! (Real users of your site will never see this, only the scrapers will.)<span class="code-snippet__tag"></<span class="code-snippet__name">p</span>></span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__tag"><<span class="code-snippet__name">a</span> <span class="code-snippet__attr">class</span>"<span class="code-snippet__attr">search-result-link</span>" <span class="code-snippet__attr">href</span>=<span class="code-snippet__string">"http://example.com/"</span>></span>Visit Now !<span class="code-snippet__tag"></<span class="code-snippet__name">a</span>></span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__tag"></<span class="code-snippet__name">div</span>></span></span></code><code><span class="code-snippet_outer">(More real search results follow)</span> |
这意味着基于 id 或 class 获取特定数据的爬虫仍然能继续工作,但是他们将会获取假数据甚至广告,而这些数据真实用户是看不到的,因为他们被 CSS 隐藏了。
对上个例子进行补充,你可以在你的 HTML 里面增加不可见的蜜罐数据来抓住爬虫。下面的例子来补充之前说的搜索结果:
1 |
<span class="code-snippet_outer"><div <span class="code-snippet__class"><span class="code-snippet__keyword">class</span>="<span class="code-snippet__title">search</span>-<span class="code-snippet__title">result</span>" <span class="code-snippet__title">style</span>="<span class="code-snippet__title">display</span>:<span class="code-snippet__type">none"></span></span></span></code><code><span class="code-snippet_outer"> <h3 <span class="code-snippet__class"><span class="code-snippet__keyword">class</span>="<span class="code-snippet__title">search</span>-<span class="code-snippet__title">result</span>-<span class="code-snippet__title">title</span>"><span class="code-snippet__title">This</span> <span class="code-snippet__title">search</span> <span class="code-snippet__title">result</span> <span class="code-snippet__title">is</span> <span class="code-snippet__title">here</span> <span class="code-snippet__title">to</span> <span class="code-snippet__title">prevent</span> <span class="code-snippet__title">scraping</span><<span class="code-snippet__type">/h3</span>></span></span></code><code><span class="code-snippet_outer"> <p <span class="code-snippet__class"><span class="code-snippet__keyword">class</span>="<span class="code-snippet__title">search</span>-<span class="code-snippet__title">result</span>-<span class="code-snippet__title">excerpt</span>"><span class="code-snippet__title">If</span> <span class="code-snippet__title">you</span>'<span class="code-snippet__title">re</span> <span class="code-snippet__title">a</span> <span class="code-snippet__title">human</span> <span class="code-snippet__title">and</span> <span class="code-snippet__title">see</span> <span class="code-snippet__title">this</span>, <span class="code-snippet__type">please ignore it. If you're a scraper</span>, <span class="code-snippet__type">please click the link below :-)</span></span></span></code><code><span class="code-snippet_outer"> Note that clicking the link below will block access to <span class="code-snippet__keyword">this</span> site <span class="code-snippet__keyword">for</span> <span class="code-snippet__number">24</span> hours.</p></span></code><code><span class="code-snippet_outer"> <a <span class="code-snippet__class"><span class="code-snippet__keyword">class</span>"<span class="code-snippet__title">search</span>-<span class="code-snippet__title">result</span>-<span class="code-snippet__title">link</span>" <span class="code-snippet__title">href</span>="/<span class="code-snippet__title">scrapertrap</span>/<span class="code-snippet__title">scrapertrap</span>.<span class="code-snippet__title">php</span>"><span class="code-snippet__title">I</span>'<span class="code-snippet__title">m</span> <span class="code-snippet__title">a</span> <span class="code-snippet__title">scraper</span> !<<span class="code-snippet__type">/a</span>></span></span></code><code><span class="code-snippet_outer"></div></span></code><code><span class="code-snippet_outer">(The <span class="code-snippet__keyword">actual</span>, real, search results follow.)</span> |
一个来获取所有内容的爬虫将会被找到,就像获取其他结果一样,访问链接,查找想要的内容。一个真人将不会看到(因为使用 CSS 隐藏),而且更不会访问这个链接。而正规或者期望的蜘蛛比如谷歌的蜘蛛将不会访问这个链接,因为你可以将 /scrapertrap/
加入你的 robots.txt 中(不要忘记增加)
你可以让你的 scrapertrap.php
做一些比如限制这个 IP 访问的事情,或者强制这个 IP 之后的请求出验证码。
/scrapertrap/
的规则避免所有的搜索引擎不会中招style
的节点。并且只是偶尔启用它, 这样爬虫一开始正常工作,但是过段时间就不起作用。这个同样也适用于上个例子。[img]http://yoursite.com/scrapertrap/scrapertrap.php[img]
,然后当正常用户访问然后点击到你的蜜罐链接。所以上个注意事项中经常更换你的蜜罐链接是非常重要的,当然你也可以检查 Referer。如果你确定某个访问是爬虫,你可以提供假的或者无用的数据;这将破坏爬虫从你网站获取的数据。你还应该把它和真实数据进行混淆,这样爬虫就不知道他们获取的到底是真的还是假的数据。
举个例子:如果你有一个新闻站,你检测到了一个爬虫,不要去直接封禁它,而是提供假的或者随机生成的文章,那爬虫获取的数据将会被破坏。如果你将假数据和真实的数据进行混淆,那爬虫很难获取到他们想要的真实文章。
很多懒惰的程序员不会在他们的爬虫发请求时带上 UserAgent,而所有的浏览器包括搜索引擎蜘蛛都会携带。
如果请求你时没有携带 UserAgent header 头,你可以展示验证码,或者直接封禁或者限制访问(或者像上面说的提供假数据或者其他的)
这个非常简单去避免,但是作为一种针对书写不当的爬虫,是值得去做的。
很多情况下,爬虫将会使用真实浏览器或搜索引擎爬虫绝对不会使用的 UserAgent,比如:
如果你发现一些爬虫使用真实浏览器或合法蜘蛛绝对不会使用的 UserAgent,你可以将其添加到你的黑名单中。
对上一章节的补充,你也可以检查 [Referer] (https://en.wikipedia.org/wiki/HTTP_referer header) (是的,它是 Referer,而不是 Referrer),一些懒惰的爬虫可能不会携带的这个,或者只是每次都携带一样的(有时候是 “google.com”)。举个例子,如果用户是从站内搜索结果页点击进入文章详情的,那要检查 Referer 这个 header 头是否存在还要看搜索结果页的打点。
注意:
还是,作为一种防止简单爬虫的方法,也值得去实现。
一个真实浏览器(通常)会请求和下载资源比如 CSS 和 图片。HTML 解析器和爬取器可能只关心特定的页面和内容。
你可以基于访问你资源的日志,如果你看到很多请求只请求 HTML,那它可能就是爬虫。
注意搜索引擎蜘蛛、旧的移动设备、屏幕阅读器和设置错误的设备可能也不会请求资源。
访问你网站时,你可以要求 cookie 必须开启。这个能识别没有经验和爬虫新手,然而爬虫要携带 Cookie 也十分简单。如果你要求开启 Cookie,你能使用它们来追踪用户和爬虫的行为,然后基于此来实现频率限制、封禁、或者显示验证码而不仅仅依赖 IP 地址。
举个例子:当用户进行搜索时,设置一个唯一的 Cookie。当搜索结果加载出来之后,验证这个 Cookie。如果一个用户打开了所有的搜索结果(可以从 Cookie 中得知),那很可能就是爬虫
使用 Cookie 可能是低效的,因为爬虫也可以携带 Cookie 发送请求,也可以根据需要丢弃。如果你的网站只能在开启 Cookie 时使用,你也不能给关闭 Cookie 的用户提供服务。
要注意如果你使用 JavaScript 去设置和检测 Cookie,你能封禁那些没有运行 JavaScript 的爬虫,因为它们没办法获取和发送 Cookie。
你可以在页面加载完成之后,使用 JavaScript + AJAX 来加载你的内容。这个对于那些没有运行 JavaScript 的 HTML 分析器来说将无法取得数据。这个对于没有经验或者新手程序员写的爬虫非常有效。
注意:
如果你用 Ajax 和 JavaScript 加载你的数据,在传输的时候要混淆一下。比如,你可以在服务器端 encode 你的数据(比如简单的使用 base64 或 负载一些的多次混淆、位偏移或者是进行加密),然后在客户端在 Ajax 获取数据之后再进行 decode。这意味着如果有人要抓包获取你的请求就不能直接看到你页面如何加载数据,而且那些人也不能直接通过 API 获得你的数据,如果想要获取数据,就必须要去解密你的算法。
下面是一些这个方式的缺点:
比如,CloudFlare 提供反蜘蛛和反爬虫的防御,你只需要直接启用它就可以了,另外 AWS 也提供类似服务。而且 Apache 的 mod_evasive 模块也能让你很轻松地实现频率限制。
你应该直接告诉人们不要抓取你的网站,比如,在你的服务条款中表明。有些人确实会尊重它,而且不在你允许的情况下不会再去抓取数据。
律师们知道如何处理侵犯版权的事情,而且他们可以发送律师函。DMCA(译者注:Digital Millennium Copyright Act,数字千年版权法案,是一个美国版权法律) 也能提供援助。
这看起来适得其反,但是你可以要求标明来源并包含返回你站点的链接。甚至也可以售卖你的 API 而赚取费用。
还有就是,Stack Exchange 提供了 API,但是必须要标明来源。
以我自己写和帮忙别人写爬虫的经验,我认为最有效的方法是:
扩展阅读:
最后祝你在保护你网站的内容坎坷路上一路顺风…
from:https://mp.weixin.qq.com/s?__biz=MzA5ODIxODE2Ng==&mid=2651137205&idx=1&sn=664a46d66f132c96780750d4e9b206eb