天天看點

随手用python寫一個下載下傳jdk源碼爬蟲

  最近在研讀jdk源碼,網上找了下資源,發現都不完整。

  後來新發現了一個有完整源碼的地方,主要包括了java,c,c++的東西,裝逼需要,就想拿來玩玩。但是,找了好多種下載下傳打開的方式,發現都不對。于是,我随手寫了python爬蟲,把他搞定。

1. 思路分析

  1.1. 目标位址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/dddb1b026323/,打開後先自己看下,是否符合自己的需求;

  1.2. 分析此結構下主要有兩種形式,一是目錄檔案,二是最終檔案,特征明顯,可區分出最終結果;

  1.3. 目錄深度不确定,很自然地想到了遞歸;

  1.4. 查詢有效目錄,很自然地想到了正規表達式;

  1.5. 基于可能有中斷的情況,可能需要進行斷點下載下傳,是以考慮加個簡單的跳過功能;

      1.6. 考慮到可能并發下載下傳,為節省時間,應使用鎖避免;

  1.7. 考慮可能出現重複下載下傳某檔案或目錄的情況,耗費資源,是以加一個全局檔案集,進行去重處理;

  1.8. 由于該檔案目錄很規律,就直接沿用其目錄結構了;

  1.9. 考慮到本機環境可能不穩定,于是利用公司測試環境伺服器;

  2.0. 開工!

2. 魯棒的代碼來一波

  python2 版本的拉取代碼如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import urllib,urllib2
import re
import os
import HTMLParser
dirbase = '/tmp'
urlbase = 'http://hg.openjdk.java.net'
url= urlbase + '/jdk8u/jdk8u/jdk/file/dddb1b026323/src'        #/jdk,/hotspot
skip_to_p = ''
skip_find = False;
textmod ={'user':'admin','password':'admin'}
textmod = urllib.urlencode(textmod)
print(url)
req = urllib2.Request(url = '%s%s%s' % (url,'?',textmod))
res = urllib2.urlopen(req)
res = res.read()
alink = re.findall(r'<a',res)
allflist = []

table=re.findall(r'<tbody class="stripes2">(.+)<\/tbody>',res, re.S)

harr = re.findall(r'href="(/jdk8u[\w\/\._]+)">(?!\[up\])', table[0])

def down_src_recursion(harr):
  global allflist,skip_find;
  if(not harr):
    return False;
  i=0; arrlen = len(harr)
  lock_conflict_jump_max = 2;   # 遇到檔案鎖時跳過n個檔案,目前仍需跳過的檔案數量
  lock_conflict_jumping = 0;
  print("in new dir cur...")
  if(len(allflist) > 1500):
     print('over 1500, cut to 50 exists...')
     allflist = allflist[-800:]
  for alink in harr:
    i += 1;
    alink = alink.rstrip('/')
    if(skip_to_p and not skip_find):
    if(alink != skip_to_p):
      print('skip file, cause no find..., skip=%s,now=%s' % (skip_to_p, alink))
      continue;
    else:
      skip_find = True;
    if(alink in allflist):
      print('目錄已搜尋過:' + alink)
      continue;
    pa = dirbase + alink
    if(os.path.isfile(pa)):
      print('檔案已存在,無需下載下傳: ' + pa)
      continue;
    lockfile=pa+'.tmp'
    if(os.path.isfile(lockfile)):
    lock_conflict_jumping = lock_conflict_jump_max;
      print('檔案正在下載下傳中,跳過+%s...: %s' % (lock_conflict_jumping, lockfile))continue; 
    else:
      if(lock_conflict_jumping > 0):
         lock_conflict_jumping -= 1;
         print('檔案正在下載下傳中,跳過+%s...: %s' % (lock_conflict_jumping, lockfile))continue;
    # 首先根據字尾把下載下傳中的辨別标記好,因為網絡下載下傳時間更慢,等下載下傳好後再加辨別其實已為時已晚
    if(pa.endswith(('.gif','.jpg','.png', '.xml', '.cfg', '.properties', '.make', '.sh', '.bat', '.html', '.c','.cpp', '.h', '.hpp', '.java', '.1'))):
       os.mknod(lockfile);
    reqt = urllib2.Request(urlbase + alink)
    rest = urllib2.urlopen(reqt)
    rest = rest.read()
    allflist.append(alink)
    if(rest.find('class="sourcefirst"') > 0):
       print('這是個資源檔案:%s         %d/%d' % (alink, i, arrlen))
       if(not os.path.isfile(lockfile)):
          os.mknod(lockfile);
       filename = alink.split('/')[-1]
       linearr = re.findall(r'<span id=".+">(.+)</span>', rest)
       fileObject = open(dirbase + alink, 'w')
       for line in linearr:
          try:
            line = HTMLParser.HTMLParser().unescape(line)
          except UnicodeDecodeError as e:
            print('oops, ascii convert error accour:', e)
          fileObject.write(line + '\r\n')
       fileObject.close()
       os.remove(lockfile); 
    else:
      print('這是目錄:%s        %d/%d' % (alink, i, arrlen))
      if(not os.path.exists(pa)):
         print('建立目錄:%s' % alink)
         os.makedirs('/tmp' + alink, mode=0777)
      ta=re.findall(r'<tbody class="stripes2">(.+)<\/tbody>',rest, re.S)
      ha = re.findall(r'href="(/jdk8u[\w\/\._]+)">(?!\[up\])', ta[0])
      down_src_recursion(ha)

# go...
down_src_recursion(harr);      

  python3則稍微有些寫法不一樣,python3版本如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from urllib import parse
from urllib import request
import urllib
import re
import os
import html
dirbase = '/tmp'
urlbase = 'http://hg.openjdk.java.net'
url= urlbase + '/jdk8u/jdk8u/jdk/file/dddb1b026323/src'        #/jdk,/hotspot
skip_to_p = ''
skip_find = False;
textmod ={'user':'admin','password':'admin'}
textmod = parse.urlencode(textmod)
print(url)
req = request.Request(url = '%s%s%s' % (url,'?',textmod))
res = request.urlopen(req)
res = res.read().decode("utf-8")
alink = re.findall(r'<a',res)
allflist = []

table=re.findall(r'<tbody class="stripes2">(.+)<\/tbody>',res, re.S)

harr = re.findall(r'href="(/jdk8u[\w\/\._]+)">(?!\[up\])', table[0])

def down_src_recursion(harr):
  global allflist,skip_find;
  if(not harr):
    return False;
  i=0; arrlen = len(harr)
  lock_conflict_jump_max = 2;   # 遇到檔案鎖時跳過n個檔案,目前仍需跳過的檔案數量
  lock_conflict_jumping = 0;
  print("in new dir cur...")
  if(len(allflist) > 1500):
     print('over 1500, cut to 50 exists...')
     allflist = allflist[-800:]
  for alink in harr:
    i += 1;
    alink = alink.rstrip('/')
    if(skip_to_p and not skip_find):
      if(alink != skip_to_p):
        print('skip file, cause no find..., skip=%s,now=%s' % (skip_to_p, alink))
        continue;
    else:
      skip_find = True;
    if(alink in allflist):
      print('目錄已搜尋過:' + alink)
      continue;
    pa = dirbase + alink
    if(os.path.isfile(pa)):
      print('檔案已存在,無需下載下傳: ' + pa)
      continue;
    lockfile=pa+'.tmp'
    if(os.path.isfile(lockfile)):
      lock_conflict_jumping = lock_conflict_jump_max;
      print('檔案正在下載下傳中,跳過+%s...: %s' % (lock_conflict_jumping, lockfile))
      continue; 
    else:
      if(lock_conflict_jumping > 0):         
        lock_conflict_jumping -= 1;
        print('檔案正在下載下傳中,跳過+%s...: %s' % (lock_conflict_jumping, lockfile))
        continue;
    # 首先根據字尾把下載下傳中的辨別标記好,因為網絡下載下傳時間更慢,等下載下傳好後再加辨別其實已為時已晚
    if(pa.endswith(('.gif','.jpg','.png', '.xml', '.cfg', '.properties', '.make', '.sh', '.bat', '.html', '.c','.cpp', '.h', '.hpp', '.java', '.1'))):
       # os.mknod(lockfile);
       open(lockfile, 'w')
    reqt = request.Request(urlbase + alink)
    rest = request.urlopen(reqt)
    rest = rest.read().decode("UTF-8")
    allflist.append(alink)
    if(rest.find('class="sourcefirst"') > 0):
       print('這是個資源檔案:%s         %d/%d' % (alink, i, arrlen))
       if(not os.path.isfile(lockfile)):
          # os.mknod(lockfile);
          open(lockfile, 'w')
       filename = alink.split('/')[-1]
       linearr = re.findall(r'<span id=".+">(.+)</span>', rest)
       fileObject = open(dirbase + alink, 'w')
       for line in linearr:
          try:
            line = html.unescape(line)
          except UnicodeDecodeError as e:
            print('oops, ascii convert error accour:', e)
          fileObject.write(line + '\r\n')
       fileObject.close()
       os.remove(lockfile); 
    else:
      print('這是目錄:%s        %d/%d' % (alink, i, arrlen))
      if(not os.path.exists(pa)):
        print('建立目錄:%s' % alink)
        os.makedirs('/tmp' + alink, mode=777)
      ta=re.findall(r'<tbody class="stripes2">(.+)<\/tbody>',rest, re.S)
      ha = re.findall(r'href="(/jdk8u[\w\/\._]+)">(?!\[up\])', ta[0])
      down_src_recursion(ha)

# go...
down_src_recursion(harr);      

   python2 與 python3 有許多差别,這是比較頭疼的地方。這一點,java就做得很好,幾十年以來變化都是相容的。但這也導緻語言維護更困難吧。python3中抛棄了許多python2的接口,包也做了轉移。頭疼!

3. 讓代碼跑起來

python jdk-crawler.py      
随手用python寫一個下載下傳jdk源碼爬蟲

  因為本段代碼是單線程的,是以并發度為1. 是以,下載下傳的快慢完全取決于網絡io.如果想下載下傳快點,那麼可以開幾個視窗,調用幾次本段python腳本,進而達到并發執行的效果。因為上段代碼中,使用了檔案鎖的方式,是以可以保證多程序之間安全并發。

4. 瞅瞅下載下傳得咋樣了

du -sh /tmp/jdk8u/      

ok, 以上,就打完了。等測試環境下載下傳完成後,再通過ftp搬到你電腦上了。

不要害怕今日的苦,你要相信明天,更苦!