刷票小程序原理剖析(python和java)

你還在苦苦拉票嗎?

前言

如今生活中離不開各種的比賽,然而,各個比賽離不開投票,咱們會清一色有時候找到咱們的朋友在朋友圈發—幫寶貝投一票,幫某某老師,學生投一票。又或許你本身參加比賽,你在爲你本身拉票。java

剖析投票原理

做爲一名程序員,你是否想爲本身的生活開一點G呢?熟悉網絡請求的咱們,應該從問題根源分析問題。對於一個投票的網站。大體分爲兩類:node

  • 登陸類: 這類網站是確實不太好操做,通常是每一個帳戶天天可以刷若干票。由於帳戶的資源是有限的,咱們很難經過獲取大量的帳戶資源爲咱們服務。何況,通常的登陸稍微大廠或者技術高點其中的js加密會比較複雜,對於普通人js水平不高很難行的通這條路。好比常見須要微信登陸,qq登錄的網站投票,就很難開掛。
  • 非登陸類: 並非全部網站都有騰訊的登陸受權的,有不少他們本身的官網他們本身就是一個體系。這類網站普通人或許也感受不到差別:投幾票以後也不能投。而後紛紛找朋友幫忙投。剖析這類網站,既然沒有登陸機制,那麼它就是根據ip機制進行鑑定。由於正常你的公網ip相對來講是穩定。因此正常一個用戶只能投固定的幾票。或許高級一點他會和瀏覽器信息結合鑑定,但這種仍是比較少的。

處理思路

既然原理已經剖析完成,那麼剩下的就是設計程序的問題了,對於一個點擊投票的事件,它的實質就是一次http(post)請求,而後後臺對數據進行更改。那麼咱們就能夠對這個操做流程進行抓包,分析這個請求是那種類型,須要那些參數。而後根據這個請求模擬寫出請求。python

然而最重要的就是ip代理,你要用代理的ip去訪問那個接口,讓對方覺得是你代理的那個ip再對他訪問,因此你須要==維護一個代理ip池==。對於代理ip池,並非什麼高大上的東西,準確的來講就是一個集合中包含一些可用的ip,可以供我使用。市面上也有不少出售代理ip,也不貴。我用的是蘑菇代理。git

具體實戰

主要流程

碰巧,最近參加的一個比賽就有拉票環節,若是人爲手動拉票的話效率地下,而且你確定也不會願意每天去舔人家求情。那就本身分析一波!程序員

  1. 首先,打開你在的網站(有的手機端,電腦端好抓包可調),谷歌或者其餘瀏覽器F12抓包,點擊network,xhr準備(確定是ajax請求不用想)。github

    在這裏插入圖片描述

  2. 分析這個請求的重要參數.(header)ajax

    在這裏插入圖片描述
    找到url和幾個參數,就能夠準備程序了。模擬請求了

具體細節python

由於這是屢次請求,因此要考慮性能的問題和效率問題。不能讓異常漫天飛,中斷,ip白白浪費,或者苦苦等待吧。 對於代理ip,各家賣的雖然有些差別可是大致相同。大體均爲賣數量,而後每一個ip從開始被用後可以維持幾分鐘的使用。而且有的ip是不能用的,有的是高延遲的,這些在寫程序的時候都要過濾掉。這裏面就要考慮下這個程序額設計。redis

  1. 多線程: python雖然多線程有個全局鎖大大的影響效率。可是對於io請求型多線程仍是能有必定的提速的。由於io有大量的線程等待。多線程的模塊大體爲定義一個線程類,定義初始方法和run函數。而後在外面定義幾個線程,讓他們跑任務。json

  2. ip處理和資源處理 正常的ip代理是一羣ip隨機抽取其中做爲代理ip,進行爬取任務,而後ip失效從ip池中刪除。而url的連接通常放到線程安全的全局容器中一個個拋出。ip放到list或者redis中進行維護,作好try catch異常便可。可是這個刷票只有一個url。而且一個ip只能用有限次數。因此換個思路,url不須要容器維護。而ip用隊列維護最好,而且python的隊列是線程安全的。因此整個程序的架構也就很清晰了。只須要用個queue解析ip獲取的格式進行相應儲存。而後被消費,當少於必定個數時,請求api獲取ip進行填充。api

  3. 在預處理方面,之前介紹過另外一個蘑菇代理使用和ip池相似的問題,能夠預先參考。

代碼實例python

import requests
import random
import time
import threading
from queue import Queue
def loadip():##從代理ip中獲取ip 一次若干擴充到queue中
    url2 = 'http://piping.mogumiao.com/proxy/api/get_ip_al?appKey=f16367295e284173ae450f&count=20&expiryDate=0&format=1&newLine=2'
    req = requests.get(url2)
    date = req.json()
    if(date['code'])!='3001':
        ipdate2 = date['msg']
        for va in ipdate2:
          que.put(va)

class downspider(threading.Thread):##線程類
    def __init__(self, threadname, que):
        threading.Thread.__init__(self)
        self.threadname = threadname
        self.que = que

    def run(self):
        print('start thread' + self.threadname)
        while True:
            try:
                toupiaospider(que,self.threadname)##投票函數
            except Exception as e:
                print(e,'888')
                break
def getproxies():#獲取ip 拼接成須要的代理格式
    b=que.get()
    d = '%s:%s' % (b['ip'], b['port'])
    global proxies
    proxies['http'] = d
    return proxies
def toupiaospider(que,threadname):
    if (que.qsize() < 15):  # 拓展ip池
        loadip()
    proxies2=getproxies()
    for i in range(0,5):
        try:
            #formData['times']=i
            req = requests.post(url, headers=header, data=formData, proxies=proxies2, timeout=1.5)
            res = req.json()
            if res['res']==2001 or req.status_code!=200:
                 continue
            print(threadname,proxies2['http'],res,que.qsize())
        except Exception as e:
            print('errror',e)

if __name__ == '__main__':
    
    proxies = {'http': ''}
    stadus = 0
    que = Queue()
    threads=[]#線程
    url='http://yunxin.163.com/api/vote/update'
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
    formData = {
        'Referer':'http://yunxin.163.com/promotion/minichallenge/gallery?from=groupmessage&isappinstalled=0',
        'id':'17',
        'times':'1',
        'activity':'minichallenge1'
    }
    proxies = {'http': '182.247.92.99:21136',
               }
    loadip()
    time.sleep(5)
    ##線程數組 ->啓動 ——>等待join
    threadList = ['thread-1','thread-2','thread-3','thread-4','thread-4','thread-5']
    for j in threadList:
        thread = downspider(j, que)
        thread.start()
        threads.append(thread)
    for t in threads:
        t.join()


複製代碼

結果

在這裏插入圖片描述

具體細節java

在java中比較棘手的就是java自身對json和http請求的處理不太方便,須要藉助第三方jar,而且一些操做稍顯的繁瑣。

首先java要弄清幾點:

  1. 代理方式: java主要有兩種代理方式,一種是jdk全局代理,另外一種是net包下的proxy代理。對於多線程程序而且ip只能用一次的固然是用net的proxy代理。
  2. 解析json 經過api獲取ip,格式固定的,須要藉助fastjson解析json串獲取須要的信息。
  3. 線程安全問題。你能夠用線程安全的blockqueue,固然其實你能夠在操做隊列的方法加上synchronized關鍵字也能夠。你能夠定義固定的線程每一個線程任務多個。也能夠用線程池定義多個線程類,每一個線程完成一個任務。
  4. 網絡請求雖然urlconnection能夠實現,可是太繁瑣,遠比jsoup複雜。因此這裏使用jsoup。

針對上面的問題。寫了個demo測試進行預備,對於獲取ip的api,大體這種格式

在這裏插入圖片描述
首先你要下載fastjson和jsoup的jar包。或者加入maven依賴。(可在maven官網下jar包)

<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.12.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
複製代碼

而後寫個demo跑一下

package com.bigsai;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test2 {
    static  int va=1;
    public static void main(String[] args) {
        String ti="{\"code\":\"0\",\"msg\":[{\"port\":\"40034\",\"ip\":\"114.237.64.247\"},{\"port\":\"33257\",\"ip\":\"223.240.210.250\"},{\"port\":\"39618\",\"ip\":\"113.101.255.11\"},{\"port\":\"43151\",\"ip\":\"183.135.106.62\"},{\"port\":\"41795\",\"ip\":\"182.108.44.227\"}]}";
        JSONObject jsonObject= JSON.parseObject(ti);
        String code=(String) jsonObject.get("code");
        JSONArray jsonArray=jsonObject.getJSONArray("msg");

        for(Object te:jsonArray)
        {
            JSONObject team=(JSONObject) te;
            String ip=team.getString("ip");
            int port=team.getInteger("port");
            System.out.println(team+" "+ip+" "+port);
        }
        ExecutorService ex= Executors.newFixedThreadPool(10);
        for(int i=0;i<200;i++)
        {
            threadtest threadtest=new threadtest();
            ex.execute(threadtest);
        }
        ex.shutdown();
    }
    static synchronized void addva()//去掉註釋試試 {
        va++;
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" "+va);
    }
    static  class threadtest implements Runnable{

        @Override
        public void run() {

            addva();
        }
    }
}

複製代碼

觀察結果。打印,這些邊角問題你就明白了。就能夠設計java程序了。

代碼實現java

package com.bigsai;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class wangyi {

    //改爲你本身的token已經被我改了
    static String ipurl="http://piping.mogumiao.com/proxy/api/get_ip_al?appKey=f16367295e284173ae3&count=20&expiryDate=0&format=1&newLine=2";

    /** * java的多線程和python略有不一樣,但也能夠改爲類似的 * py是5個線程每一個線程死循環任務,而java用線程池每一個線程一個任務,只不過new了不少對象 * @param args */

    static  Queue<proxynode> q1;
    public static void main(String[] args) {


       q1=new ArrayDeque();//隊列存放結構體 ip和port
        ExecutorService ex= Executors.newFixedThreadPool(10);
        for(int i=0;i<200;i++)
        {
            try {
                Proxy proxy=getproxies(q1);//得到代理ip
                vote vote=new vote(proxy);
                ex.execute(vote);
            }
            catch (Exception e)
            {e.printStackTrace();}
        }
        ex.shutdown();


    }
    static synchronized Proxy getproxies(Queue<proxynode> q1) throws IOException//上鎖得到代理,由於ip只用一次,也可以使用自帶的線程安全隊列 {

       if(q1.size()<15)//擴充ip池
       {
           String jsonva=Jsoup.connect(ipurl).timeout(2500).get().text();
           JSONObject jsonObject= JSON.parseObject(jsonva);
           String code=(String) jsonObject.get("code");//狀態嗎
           if (code.equals("0")) {//正常返回接口
               JSONArray jsonArray = jsonObject.getJSONArray("msg");

               for (Object jsonobj: jsonArray) {
                   JSONObject team = (JSONObject) jsonobj;
                   String ip = team.getString("ip");
                   int port = team.getInteger("port");
                   proxynode node=new proxynode(ip,port);
                   q1.add(node);
               }
           }
           else return null;
       }
        proxynode proxynode=q1.poll();
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxynode.ip, proxynode.port));
        return proxy;
    }
    static  class proxynode//一個node儲存ip和端口 {
        String ip;
        int port;
        proxynode(String ip,int port)
        {
            this.ip=ip;
            this.port=port;
        }

    }
    static class vote implements Runnable{

        Proxy proxy;
        vote(Proxy proxy)  {
            this.proxy=proxy;
        }
        public void dovote() throws IOException {

            //Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("host", 8888));
            try {

                Connection connect = Jsoup.connect("http://yunxin.163.com/api/vote/update").timeout(2000);
                Map<String,String>date=new HashMap<String, String>();
                date.put("id","17");
                date.put("times","1");
                date.put("activity","minichallenge1");
                date.put("Referer","http://yunxin.163.com/promotion/minichallenge/gallery?from=groupmessage&isappinstalled=0");
                date.put("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
                connect.data(date);
                connect.ignoreContentType(true);
                connect.proxy(proxy);
                Document doc=connect.post();
                System.out.println(Thread.currentThread().getName()+" "+proxy.address()+" "+doc.text());
            }
            catch (Exception e)
            {
                System.out.println(e.toString());
            }

        }
        public void run() {

            try {
                for(int i=0;i<5;i++) {
                    dovote();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

複製代碼

結果

在這裏插入圖片描述

總結

在寫爬蟲仍是python方便和簡單。由於py對json支持較好(字典),而java強對象類型語法要求較嚴。可是在多線程方面java確定是秒殺py的。由於py的多線程是(假)多線程。想提升速度的能夠研究多進程。

這類問題本質不難的,作過一次就很簡單了。這只是其中一種案例。提供一些思想和解決思路。遇到不一樣的問題可能須要不一樣的結構,方式去解決,這就須要融匯貫通。

若是有錯誤,請大佬指正。

  • github代碼地址,歡迎star

  • 歡迎關注個人我的公衆號:bigsai
  • 相關文章
    相關標籤/搜索