刷票小程式案例原理剖析(python和java)

  • 2019 年 10 月 6 日
  • 筆記
  • 前言
  • 剖析投票原理
  • 處理思路
  • 具體實戰
    • 主要流程
  • 具體細節python
  • 程式碼實例python
  • 具體細節java
  • 程式碼實現java
  • 總結

前言

現在生活中離不開各類的比賽,然而,各個比賽離不開投票,我們會清一色有時候找到我們的朋友在朋友圈發—幫寶貝投一票,幫某某老師,學生投一票。又或許你自己參加比賽,你在為你自己拉票。

剖析投票原理

作為一名程式設計師,你是否想為自己的生活開一點G呢?熟悉網路請求的我們,應該從問題根源分析問題。對於一個投票的網站。大致分為兩類:

  • 登錄類: 這類網站是確實不太好操作,一般是每個賬戶每天能夠刷若干票。因為賬戶的資源是有限的,我們很難通過獲取大量的賬戶資源為我們服務。況且,一般的登錄稍微大廠或者技術高點其中的js加密會比較複雜,對於普通人js水平不高很難行的通這條路。比如常見需要微信登錄,qq登陸的網站投票,就很難開掛。
  • 非登錄類: 並不是所有網站都有騰訊的登錄授權的,有很多他們自己的官網他們自己就是一個體系。這類網站普通人或許也感覺不到差異:投幾票之後也不能投。然後紛紛找朋友幫忙投。剖析這類網站,既然沒有登錄機制,那麼它就是根據ip機制進行鑒定。因為正常你的公網ip相對來說是穩定。所以正常一個用戶只能投固定的幾票。或許高級一點他會和瀏覽器資訊結合鑒定,但這種還是比較少的。

處理思路

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

然而最重要的就是ip代理,你要用代理的ip去訪問那個介面,讓對方以為是你代理的那個ip再對他訪問,所以你需要維護一個代理ip池。對於代理ip池,並不是什麼高大上的東西,準確的來說就是一個集合中包含一些可用的ip,能夠供我使用。市面上也有很多出售代理ip,也不貴。我用的是蘑菇代理。

具體實戰

主要流程

碰巧,最近參加的一個比賽就有拉票環節,如果人為手動拉票的話效率地下,並且你肯定也不會願意天天去舔人家求情。那就自己分析一波!

  1. 首先,打開你在的網站(有的手機端,電腦端好抓包可調),Google或者其他瀏覽器F12抓包,點擊network,xhr準備(肯定是ajax請求不用想)。
  1. 分析這個請求的重要參數.(header)

找到url和幾個參數,就可以準備程式了。模擬請求了

具體細節python

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

  1. 多執行緒: python雖然多執行緒有個全局鎖大大的影響效率。但是對於io請求型多執行緒還是能有一定的提速的。因為io有大量的執行緒等待。多執行緒的模組大致為定義一個執行緒類,定義初始方法和run函數。然後在外面定義幾個執行緒,讓他們跑任務。
  2. ip處理和資源處理 正常的ip代理是一群ip隨機抽取其中作為代理ip,進行爬取任務,然後ip失效從ip池中刪除。而url的鏈接一般放到執行緒安全的全局容器中一個個拋出。ip放到list或者redis中進行維護,做好try catch異常即可。但是這個刷票只有一個url。並且一個ip只能用有限次數。所以換個思路,url不需要容器維護。而ip用隊列維護最好,並且python的隊列是執行緒安全的。所以整個程式的架構也就很清晰了。只需要用個queue解析ip獲取的格式進行相應儲存。然後被消費,當少於一定個數時,請求api獲取ip進行填充。
  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的多執行緒是(假)多執行緒。想提高速度的可以研究多進程。

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

如果有錯誤,請大佬指正。