爬虫从入门到放弃——反爬虫理论基础

对于一张网页,我们往往希望它是结构良好,内容清晰的,这样搜索引擎才能准确地认知它。
而反过来,又有一些情景,我们不希望内容能被轻易获取,比方说电商网站的交易额,教育网站的题目等。因为这些内容,往往是一个产品的生命线,必须做到有效地保护。这就是爬虫与反爬虫这一话题的由来。

常见反爬虫策略

但是世界上没有一个网站,能做到完美地反爬虫。

如果页面希望能在用户面前正常展示,同时又不给爬虫机会,就必须要做到识别真人与机器人。因此工程师们做了各种尝试,这些策略大多采用于后端,也是目前比较常规单有效的手段,比如:

  1. User-Agent + Referer检测
  2. 账号及Cookie验证
  3. 验证码
  4. IP限制频次

我们详细介绍一下以上的内容。


  • Referer检测:

Referer是http协议中定义的,Referer就是上一个页面的地址,这个是浏览器会在点击一个链接时自动添加到请求头中的:(查看一个request信息)

GET /adjs.php?n=348893119&what=zone:382&charset=utf-8&exclude=,&referer=http%3A//topic.csdn.net/u/20100730/16/fd41a3a3-8976-49ce-b32e-65011241e626.html HTTP/1.1
Host: z.csdn.net
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
Accept: */*
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://topic.csdn.net/u/t5/include/ad1.asp?pdate=2010-07-30%2016:47:51&ba=Java&sa=J2EE

最下面就是referer的值.
那referer有什么用呢?
1,防止盗连,比如我是个下载软件的网站,在下载页面我先用referer来判断上一页面是不是自己网站,如果不是,说明有人盗连了你的下载地址。
2,电子商务网站的安全,我在提交信用卡等重要信息的页面用referer来判断上一页是不是自己的网站,如果不是,可能是黑客用自己写的一个表单,来提交,为了能跳过你上一页里的javascript的验证等目的。
3,统计,比如从我主页上链接到一个朋友那里,他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问他的网站

一个防盗链代码如下:

public void doFilter(ServletRequest req, ServletResponse res,  
            FilterChain chain) throws ServletException, IOException {  

        HttpServletRequest request = (HttpServletRequest) req;  
        HttpServletResponse response = (HttpServletResponse) res;  
        // 禁止缓存  
        response.setHeader("Cache-Control", "no-store");  
        response.setHeader("Pragrma", "no-cache");  
        response.setDateHeader("Expires", 0);  
        // 链接来源地址  
        String referer = request.getHeader("referer");  
        System.out.println("refer is"+referer);  
        System.out.println("serverName is"+request.getServerName());  
        if (referer == null || !referer.contains(request.getServerName())) {  
        /** 
        * 如果 链接地址来自其他网站,则返回错误图片 
        */  
        request.getRequestDispatcher("/error.jsp").forward(request, response);  
        } else {  
        /** 
        * 图片正常显示 
        */  
        chain.doFilter(request, response);  
        }  
    } 

不过referer不是安全的,在Java中获取一个网站的HTML内容可以通过HttpURLConnection来获取.我们在HttpURLConnection中可以设置referer来伪造referer,轻松绕过这类防采集的网站:

HttpURLConnection conn = (HttpURLConnection) newURL(path).openConnection();  
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible;MSIE 7.0; Windows NT 5.1; Maxthon;)");  
conn.setRequestProperty("Accept-Encoding", "gzip");  
conn.setRequestProperty("referer","http://control.blog.sina.com.cn/selienceblog");  
conn.setRequestProperty("cookie","http://control.blog.sina.com.cn");  
InputStream inputStream = conn.getInputStream();  
//保存inputstream中的东西就OK了

所以由上面两点,也就是User-Agent + Referer检测以及账号及Cookie验证,在Website中如何设置呢?
Site类!
对站点本身的一些配置信息,例如编码、HTTP头、超时时间、重试策略等、代理等,都可以通过设置Site对象来进行配置。
在这里插入图片描述
所以IP限制频次也可以通过上面一些操作实现了,主要是通过以下两种方法。
方法1:

尽可能的模拟用户行为:
1、UserAgent经常换一换
2、访问时间间隔设长一点,访问时间设置为随机数;
3、访问页面的顺序也可以随机着来

方法2:

1 user agent 伪装和轮换
2 使用代理 ip 和轮换
3 cookies 的处理,有的网站对登陆用户政策宽松些

而爬虫是可以无限逼近于真人的,比如:

盘点一些网站的反爬虫机制

网络爬虫的难点其实并不在于爬虫本身。而是网站方为了避免数据被爬取,增加了各种各样的反爬虫措施。如果想要继续从网站爬取数据就必须绕过这些措施。因此,网络爬虫的难点在于反爬的攻克和处理。那么本文主要介绍一些网站的反爬虫措施。

妹子图
这个网站的反爬虫机制比较简单。当我们使用网络请求库下载图片时,该网站会对检查每个 HTTP 请求的 headers 头部中 Referer 字段。它判断该字段是否为空,如果字段为空,那么不会返回正常显示的图片,而是返回一张带有“图片来自妹子网,请勿盗链”字样的图片。

遇到这种机制,突破也是比较简单。对每个 HTTP 请求,将页面的 url 地址填充到 Referer 字段中。

豆瓣
几乎所有的爬虫新手都会爬取豆瓣练练手。但是豆瓣还是保持开放的态度,反爬虫机制做得还是很人性化。它的反爬虫机制大概如下:

1、在没有携带 cookie 的情况下,如果某个 IP 短时间高并发请求网站,该 IP 会立马被封。当 IP 被封,登录豆瓣网站会解封。
3、在携带 cookie 的情况下,某个 IP 请求网站过于频繁。豆瓣的反爬虫机制变为只封 cookie 不封 IP。也就说退出登录或者换个账号还能继续访问网站。

面对这么体谅新手的网站,我们要下手不能那么猛。我们只要在代码中登录账号,同时降低并发数,再随机延迟等待一段时间。我们的爬虫程序就不会被封杀了。

拉勾网
拉勾网站刚出来的时候,反爬虫机制还没有现在这么严格。估计爬取网站的人多了起来,网站管理员为了保护服务器增加一些手段。该网站的反爬虫机制大概是这样子。

1、在没有登录的情况下,程序只能连续访问 3 个 Url。如果再继续访问,网站会将链接重定向,然后提示我们登录。
2、如果在登录情况下,连续请求部分 url 之后,我们的 IP 会被封。

针对这样的爬虫机制,我们只能使用 IP 代理池来突破。

汽车之家
汽车之家论坛的反爬虫机制就比较高级。它利用前端页面自定义字体的方式来实现反爬的技术手段。具体使用到是 CSS3 中的自定义字体(@font-face)模块,自定义字体主要是实现将自定义的 Web 字体嵌入到指定网页中去。这就导致我们去爬取论坛帖子的口碑时,获取到的返回文本中每隔几个字就出现一个乱码符号。

每次访问论坛页面,其中字体是不变的,但字符编码是变化的。因此,我们需要根据每次访问动态解析字体文件。

具体可以先访问需要爬取的页面,获取字体文件的动态访问地址并下载字体,读取 js 渲染后的文本内容,替换其中的自定义字体编码为实际文本编码,就可复原网页为页面所见内容了。

最后说句良心话,我们爬取别人网站的数据,要在不损害别人网站的情况下进行。所以建议大家不要在网站访问高峰的时候爬取数据,尽量选择在晚上进行爬取。同时设置延时操作降低并发数

代码实操

根据这些反爬虫理论,我简单的写了一个读取CSDN文章的爬虫。
有一点没有解决的是,Process里面设置url的问题。

package csdn;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;
import us.codecraft.webmagic.pipeline.FilePipeline;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.scheduler.PriorityScheduler;

import java.util.ArrayList;
import java.util.List;

/**
 * Author: LeesangHyuk
 * Date: 2019/2/21 14:21
 * Description:
 */
public class CSDNProcessor implements PageProcessor {

    private Site site;
    private static int count=0;

//    public static final String URL_LIST = "";
    public static final String URL_POST = "https://blog.csdn.net/No_Game_No_Life_/article/details/[0-9]{8}";

    public void process(Page page) {
        if (page.getUrl().regex(URL_POST).match()) {
            page.putField("title",page.getHtml().xpath("//*[@id=\"mainBox\"]/main/div[1]/div/div/div[1]/h1/text()"));
            if (page.getResultItems().get("title") == null) {
                page.setSkip(true);
            }else{
                count++;
            }
            page.putField("url",page.getUrl().toString());
            page.putField("time",page.getHtml().xpath("//*[@id=\"mainBox\"]/main/div[1]/div/div/div[2]/div[1]/span[1]/text()"));

        }else{
            page.addTargetRequests(page.getHtml().links().regex(URL_POST).all(), 1000);
            page.addTargetRequests(getListLink(), 1);
        }
    }

    public Site getSite() {
        if (site==null) {
            site=Site.me().setRetryTimes(3).setSleepTime(1000).setTimeOut(10000)
                    .addHeader("referer","https://blog.csdn.net/No_Game_No_Life_")
                    .setCharset("utf-8").setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3633.400 QQBrowser/10.4.3232.400");

        }
        return site;
    }

    public List<String> getListLink(){
        List<String> links=new ArrayList<String>();
        for (int i = 2; i < 13; i++) {
            links.add("https://blog.csdn.net/No_Game_No_Life_/article/list/"+i);
        }
        return links;
    }

    public static void main(String[] args) {
        long startTime, endTime;
        System.out.println("开始爬取...");
        startTime = System.currentTimeMillis();
        Spider.create(new CSDNProcessor())
                .setScheduler(new PriorityScheduler())
                .addPipeline(new FilePipeline("D:\\英雄时刻"))
                .addUrl("https://blog.csdn.net/No_Game_No_Life_/")
                .thread(2).run();
        endTime = System.currentTimeMillis();
        System.out.println("爬取结束,耗时约" + ((endTime - startTime) / 1000) + "秒,抓取了"+count+"条记录");

    }
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值