使用代理IP地址开发某网站自动投票程序

开发某网站自动投票程序

前言

9月初我要给学生分享爬虫的技术,恰逢公司举办了《首届学员故事争霸赛》,争霸赛会通过网站的形式将学员投稿的文章展示出来,并开放了一个投票功能,活动截止后哪篇文章的得票最多,就能得奖。想着我上课时,学员肯定会问我能不能做一个自动投票的爬虫,如果没有提前准备,到时候胡乱答上一通,难免尴尬,所以……

分析网站结构

功能顺序:

  • 随机打开某篇文章详情页,在网页底部有个投票按钮
  • 点击投票案例,会弹出一个验证码的框
  • 输入成功之后,开始执行投票。

网络请求顺序:

  • GET请求连接生成验证码图片
  • POST请求连接对用户输入的数据进行校验, 成功返回值为1,失败返回值为null.
  • 如果上一个接口返回结果为1,会请求连接开始投票。投票成功返回null,同一个IP地址重复投票提示 已投票

爬虫代码分析

从网络请求顺序来开,只需要请求投票接口就可以进行投票了。
那就可以直接跳过验证码的环节,对文章进行投票啦。
经过分析,发现请求投票接口是个POST请求,但是并没有提交任何参数到服务器。那怎么告诉服务器,我投的是那篇文章呢?
在Http请求头中,我发现一个Referer的地址信息,初步判断是根据referer地址最后的数据来确定是那篇文章的,并验证正确。

POST /article/updatevote HTTP/1.1
Host: fendou.itcast.cn
Connection: keep-alive
Content-Length: 0
Accept: */*
Origin: http://fendou.itcast.cn
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Referer: http://fendou.itcast.cn/article/look/aid/xxx
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: PHPSESSID=6uvq57evo7u9b1k2v1oc4m5c91; UM_distinctid=15e0d3a45503f7-01454f44702d6b-333f5902-100200-15e0d3a4551639; CNZZDATA1263359132=494974881-1503455567-%7C1503470255

爬虫代码开发

创建maven项目,导入pom依赖

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>

代码开发

private static void dovote(String ip, int port) throws IOException, ClientProtocolException {
        //1.投票的请求接口
        String url = "http://fendou.itcast.cn/article/updatevote";
        //2.准备请求头的信息
        Map<String, String> headers = getHeader();
        //3.创建POST请求对象
        HttpPost httpPost = new HttpPost(url);
        //4.准备请求头的信息
        for (Map.Entry<String, String> header : headers.entrySet()) {
            httpPost.addHeader(header.getKey(), header.getValue());
        }
        //5.创建代理HTTP请求
        HttpHost proxy = new HttpHost(ip, port);
        ConnectionConfig connectionConfig = ConnectionConfig.custom().setBufferSize(4128).build();
        DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
        CloseableHttpClient hc = HttpClients.custom().setDefaultConnectionConfig(connectionConfig)
                .setRoutePlanner(routePlanner).build();
        //6.使用代理HttpClient发起投票请求
        CloseableHttpResponse res = hc.execute(httpPost);
        //7.打印http请求状态码
        System.out.println("statusCode:" + res.getStatusLine().getStatusCode());
        for (Header header : res.getAllHeaders()) {
            //8.打印所有的response header信息,发现有set-cookie的信息就成功了。
            System.out.println(header);
        }
        //9.打印html信息 如果返回为空字符串 就是投票成功。有返回值的基本就是失败了。
        String html = EntityUtils.toString(res.getEntity(), Charset.forName("utf-8"));
        System.out.println("返回值:" + html);
    }
private static Map<String, String> getHeader() {
        HashMap<String, String> headers = new HashMap<String, String>();
        headers.put("Referer", "http://fendou.itcast.cn/article/look/aid/xxx?qq-pf-to=pcqq.c2c");
        headers.put("host", "fendou.itcast.cn");
        headers.put("Origin", "http://fendou.itcast.cn");
        headers.put("User-Agent",
                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36");
        headers.put("X-Requested-With", "XMLHttpRequest");
        headers.put("cookie", "PHPSESSID=8bj2oolgfq23idj1n3qofmm893; UM_distinctid=15e0d154d8a617-095bb15b8b50fd-414a0229-100200-15e0d154d8b4b3");
        
        headers.put("Accept", "*/*");
        headers.put("Accept-Encoding", "gzip, deflate");
        headers.put("Accept-Language", "zh-CN,zh;q=0.8");
        headers.put("Connection", "keep-alive");
        
        return headers;
    }

为爬虫找一些代理IP

经过分析测试,发现这个爬虫会根据cookie信息和IP地址判断用户是否重复投票。
cookie信息好删除掉
IP地址就难搞了,只能使用代理IP了。
几种方式:

  • 在百度上找一些提供代理IP的网址,碰运气
  • 使用淘宝购买IP服务,本例使用的是2块钱2000个IP的服务,淘宝地址,HTTP获取IP的接口,不是很稳定,好在我只是做个测试。
  • 使用专业级的公司,提供高质量的代理IP。特点,难找,贵。 由于我只是测试,就没有继续找了。

代理找好之后,就开始使用多线程进行爬取了。
代码如下:

        /**
     * 使用多线程进行爬取
     **/
    private static void multiThread() throws FileNotFoundException, IOException, InterruptedException {
        //1.创建阻塞队列用来存放代理IP地址
        final ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(10000);
        //2.读取文件,该文件中保存着代理ip和端口号。如 103.29.186.189:3128
        String ips = FileUtil.readFileToString("C:/workspace/eclipse/mars/doVote4FenDou/src/main/resources/ips.txt");
        String[] ipkv = ips.split(" ");
        System.out.println("total ips:"+ipkv.length);
        for (String ip : ipkv) {
            System.out.println(ip);
            arrayBlockingQueue.put(ip);
        }
        int size = 10;
        //3.开启多线程进行投票
        ExecutorService pool = Executors.newFixedThreadPool(size);
        for (int i = 0; i < size; i++) {
            pool.submit(new Thread(new Runnable() {
                public void run() {
                    while (true) {
                        try {
                            String ipkv = arrayBlockingQueue.take();
                            System.out.println(ipkv);
                            String[] kv = ipkv.split(":");
                            dovote(kv[0], Integer.parseInt(kv[1]));
                        } catch (Exception e) {
                            System.out.println("请求失败!" + e);
                        }
                    }
                }
            }));
        }
    }

总结

  • 本例中的投票投票系统比较简单,只是用了cookie信息和IP地址进行防治投票。
  • 爬虫的开发重在分析思路,比如[网络请求顺序]篇幅中的几个返回值,也是反复测试的,这个阶段比价耗时,反而代码编写的时间较小。
  • 世界上有两人最可怕,一种是有聪明的坏人,一种是有权利的愚民,这两类人可以产生无穷的破坏力。期望看到文章的你,并不会拿技术去作恶。

又记

  • 我已联系网站管理员。2017年08月23日 下午4:23

又记 2017年08月23日 下午5:49

  • 和网站管理沟通之后,找到了绕开验证码直接投票的Bug。网站修复重新上线之后,需要识别验证码之后才能进行投票。
  • 和管理员沟通时,知道了一个新产品【极验】。看到这个吊炸天的功能,更加屌的时,我发现我居然可以做一个。后期找个时间,自己做一个玩玩。

搜索引擎底层核心技术Lucune(三)-使用IKAnalyzer分词器

搜索引擎底层核心技术Lucune(四)-使用IKAnalyzer分词器

IKAnalyzer 是林良益开源的一个非常著名和老牌的 Java 中文分词库,目前 OSChina 网站也是使用 IK 分词器

巫师两年前做搜索的时候第一次用到IK分词器,还通过支付宝捐赠给作者一笔钱呢。遗憾的是IKAnalyzer在2012年停止了更新,巫师找到的版本号是IKAnalyzer2012FF_u1.jar。

将IKAnalyzer分词器安装到本地的maven仓库

由于在maven项目中使用,而IKAnalyzer有没有maven的依赖,就需要将IKAnalyzer安装到本地仓库。
前置条件:需要电脑上安装好了maven环境,能够执行mvn命令。

mvn install:install-file -Dfile=IKAnalyzer2012FF_u1.jar -DgroupId=org.wltea.ik-analyzer -DartifactId=ik-analyzer -Dversion=4.10.2 -Dpackaging=jar

参数说明:

  • -Dfile 是文件路径的意思,写全路径,本例需要更改。
  • -DgroupId 组织机构的名称,本例中不用更改
  • -DartifactId 一般项目的名称,本例中不用更改
  • -Dversion 安装到maven仓库的版本号,可更改,我随便写的。
  • -Dpackaging 安装包的类型

pom文件中的依赖信息

<!-- 这里的ik分词器的版本号没有对应ik真实的版本号。ik2012年就停止更新了 -->
        <dependency>
            <groupId>org.wltea.ik-analyzer</groupId>
            <artifactId>ik-analyzer</artifactId>
            <version>4.10.2</version>
        </dependency>

创建索引时的实例代码

    public void createIndexWithIkAnaylzer() throws Exception {
        // 1. Directory 是用来存储索引信息
        Directory d = FSDirectory.open(new File("index"));
        // 2. IKAnalyzer 主要是用来分词
        IKAnalyzer analyzer = new IKAnalyzer();
        // 3、在IndexWriterConfig 需要指定我们使用的分词器
        IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
        // 4、创建一个indexWriter,需要制定索引数据保存在哪里。 指定一个IndexWriterConfig.
        IndexWriter indexWriter = new IndexWriter(d, conf);
        // 5、创建一个Docment对象
        Document doc = new Document();
        doc.add(new IntField("id", 1, Store.YES));
        doc.add(new TextField("title", "我想学习大数据知识", Store.YES));
        doc.add(new TextField("content", "如何学大数据,大数据技术哪家强", Store.YES));

        // 6.为document创建索引。
        indexWriter.addDocument(doc);
        indexWriter.close();
    }

搜索引擎底层核心技术Lucune(三)-中文分词

搜索引擎底层核心技术Lucune(三)-中文分词

中文分词的工具有很多种,参见常用的开源中文分词工具

目前结巴分词、中科院ICTCLA、清华大学THULAC、哈工大的LTP、庖丁用的普遍一些。当然也不是说其他算法分词软件不行,笔者最开始接触的IKAnalyzer用起来也很nice的。

其实中文分词要达到较好的效果,算法诚然重要,词库也是非常关键,而且词库还要与时俱进的自动更新,三年前的网络语言在今天已经是过去式了,如果词库不更新分词结果肯定是乱的一塌糊涂。

目前在大数据环境下,分词的速度又是一个新的挑战,所以速度和性能又会是一个需要权衡的点,总得有取舍。

最后附上一个经典的分词语料:工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作。

搜索引擎底层核心技术Lucune(二)-常用的几种查询方式

常用的几种查询方式

  • TermQuery词元使用
  • FuzzyQuery模糊查询使用
  • prefixQuery 通过前缀查询
  • wildcardQuery 通配符查询
  • BooleanQuery组合查询
  • 通过分词器进行查询parser
  • 通过分词器进行多字段查询

TermQuery词元使用

    public void termQueryTest() throws IOException {
    IndexSearcher indexSearcher = getIndexSearcher();
    
    //一个词元
    Term t = new Term("title", "lucene");
    TermQuery termQuery = new TermQuery(t);
    TopDocs topDocs =indexSearcher.search(termQuery, 3);
    
    printResult(indexSearcher, topDocs);
        
    }

FuzzyQuery模糊查询使用

    public void fuzzyQueryTest() throws IOException {
    IndexSearcher indexSearcher = getIndexSearcher();
    
    //模糊查询
    //通过两次置换,能够得到一个词条
    Term term = new Term("title","lucene");
    FuzzyQuery query = new FuzzyQuery(term);
    TopDocs topDocs =indexSearcher.search(query, 3);
        
    printResult(indexSearcher, topDocs);
        
    }

prefixQuery 通过前缀查询

    public void preQuery() throws IOException {
    IndexSearcher indexSearcher = getIndexSearcher();
        
    PrefixQuery query = new PrefixQuery(new Term("title","luce"));
    TopDocs topDocs =indexSearcher.search(query, 3);

    printResult(indexSearcher, topDocs);
        
    }

wildcardQuery 通配符查询

    public void wildcardQuery() throws IOException {
    IndexSearcher indexSearcher = getIndexSearcher();
    
    WildcardQuery query = new WildcardQuery(new Term("title","l*"));
    TopDocs topDocs =indexSearcher.search(query, 2);
    printResult(indexSearcher, topDocs);
    }

BooleanQuery组合查询

    public void booleanQuery() throws IOException {
    IndexSearcher indexSearcher = getIndexSearcher();
    BooleanQuery booleanQuery = new BooleanQuery();
    
        WildcardQuery wildcardQuery = new WildcardQuery(new Term("title", "l*"));
        booleanQuery.add(wildcardQuery, BooleanClause.Occur.MUST);
        
        PrefixQuery prefixQuery = new PrefixQuery(new Term("title", "m"));
    booleanQuery.add(prefixQuery, BooleanClause.Occur.MUST);

    TopDocs topDocs = indexSearcher.search(booleanQuery, Integer.MAX_VALUE);

    printResult(indexSearcher, topDocs);
    }

通过分词器进行查询parser

    public void parserQuery() throws Exception {
        IndexSearcher indexSearcher = getIndexSearcher();

    Analyzer analyzer = new IKAnalyzer();
    QueryParser queryParser = new QueryParser("content", analyzer);
    Query query =queryParser.parse("学习lucene");
        
    TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);

    printResult(indexSearcher, topDocs);
    }

通过分词器进行多字段查询

    public void multQuery() throws Exception {
    IndexSearcher indexSearcher = getIndexSearcher();

    MultiFieldQueryParser multiFieldQueryParser 
    = new MultiFieldQueryParser(new String[]{"title","content"},new IKAnalyzer());
    Query query =multiFieldQueryParser.parse("学习mac");
        
    TopDocs topDocs = indexSearcher.search(query, Integer.MAX_VALUE);

    printResult(indexSearcher, topDocs);
}