網絡上大部分關於python爬蟲的介紹以及代碼講解,都用的是python2.7或以下版本,用python3.x版本的甚少。
在python3.3.2版本中,沒有urllib2這個庫,也沒有cookiejar這個庫。對應的庫分別是http.cookiejar以及urllib這倆。
關於url以及python2.7爬蟲寫法的介紹,可以參考[Python]網絡爬蟲(一):抓取網頁的含義和URL基本構成這系列的文章。我這里主要介紹的是python3.3.2的urllib2的實現。
首先,下面是一個簡單的抓取網頁信息程序,主要是介紹下urllib的使用。
# 簡單的抓取HOJ主頁的數據
import urllib.request
response = urllib.request.urlopen('http://acm.hit.edu.cn')
html = response.read()
print(html)
# 注意,由於網頁抓取下來的是bytes格式的數據,所以寫入文件時需要以二進制的方式寫入
fout = open('txt.txt','wb')
fout.write(html)
fout.close()
fout = open('html.html','wb') # 寫入到文件html.html
fout.write(html)
fout.close()
運行結果應該是txt.txt,html.html兩個文件。我們可以打開看看txt.txt、html.html看看,發現txt.txt的數據跟瀏覽器查看的網頁源代碼一致。如果用瀏覽器打開html.html,會發現這個跟我們實際上考看到的網頁基本一樣。
模擬HOJ登陸並抓取已經AC的代碼
我們需要進行登陸,需要設置一個cookie處理器,它負責從服務器下載cookie到本地,並且在發送請求時帶上本地的cookie。
# 設置一個cookie處理器,它負責從服務器下載cookie到本地,並且在發送請求時帶上本地的cookie
cj = http.cookiejar.LWPCookieJar()
cookie_support = urllib.request.HTTPCookieProcessor(cj)
opener = urllib.request.build_opener(cookie_support, urllib.request.HTTPHandler)
urllib.request.install_opener(opener)
登陸時需要對HOJ服務器發送數據請求,對於發送data表單數據,我們怎么查看需要發送的數據?在HTTP中,這個經常使用熟知的POST請求發送。
我們可以先查看網頁源代碼,看到

看到三個name,所以發送的求情數據中需要包含這三個,
# 構造Post數據,從抓大的包里分析得出的或者通過查看網頁源代碼可以得到
data = {
'user' : user, # 你的用戶名
'password' : password, # 你的密碼,密碼可能是明文傳輸也可能是密文,如果是密文需要調用相應的加密算法加密
'submit' : 'Login' # 特有數據,不同網站可能不同
}
另外,一般的HTML表單,data需要編碼成標准形式。然后做為data參數傳到Request對象。所以我們需要對data進行編碼。
data = urllib.parse.urlencode(data).encode('utf-8')
發送請求,得到服務器給我們的響應,完成登錄功能。header最好加上,不然由於內部信息默認顯示為機器代理,可能被服務器403 Forbidden拒絕訪問。
# 發送請求,得到服務器給我們的響應
response = urllib.request.Request(url, data,header)
# 通過urllib提供的request方法來向指定Url發送我們構造的數據,並完成登錄過程
urllib.request.urlopen(response)
登錄函數如下:
def login(): # 登陸函數
print('請輸入你的賬號')
user = input()
print('請輸入你的密碼')
password = input()
# 設置一個cookie處理器,它負責從服務器下載cookie到本地,並且在發送請求時帶上本地的cookie
cj = http.cookiejar.LWPCookieJar()
cookie_support = urllib.request.HTTPCookieProcessor(cj)
opener = urllib.request.build_opener(cookie_support, urllib.request.HTTPHandler)
urllib.request.install_opener(opener)
url = 'http://acm.hit.edu.cn/hoj/system/login' # 登陸的界面
# 這個最好加上,不然由於內部信息默認顯示為機器代理,可能被服務器403 Forbidden拒絕訪問
header={'User-Agent':'Magic Browser'}
# 構造Post數據,從抓大的包里分析得出的或者通過查看網頁源代碼可以得到
data = {
'user' : user, # 你的用戶名
'password' : password, # 你的密碼,密碼可能是明文傳輸也可能是密文,如果是密文需要調用相應的加密算法加密
'submit' : 'Login' # 特有數據,不同網站可能不同
}
data = urllib.parse.urlencode(data).encode('utf-8')
# 發送請求,得到服務器給我們的響應
response = urllib.request.Request(url, data,header)
# 通過urllib提供的request方法來向指定Url發送我們構造的數據,並完成登錄過程
urllib.request.urlopen(response)
return
登錄進去后,我們需要對已經AC的代碼進行抓取。抓取的鏈接為:
http://acm.hit.edu.cn/hoj/problem/solution/?problem=題目編號
url = 'http://acm.hit.edu.cn/hoj/problem/solution/?problem='+str(i)
response = urllib.request.urlopen(url)
html = response.read()
我們可以把html輸出看看具體內容是什么。
通過查看網頁代碼,發現提交的代碼都是在<span></span>標簽中。

因此我們可以對這部分數據進行處理,處理過程中我們可以用到python的正則表達式進行搜索。並且對於特殊的編碼如<需要進行解碼。
當然,我們需要注意保存的文件到底是java還是c++。
最后,詳細的代碼如下:
import urllib
import http.cookiejar
#import time
hashTable = { # 網頁特殊編碼轉化
'<':'<',
'>': '>',
'{':'{',
'}':'}',
'(':'(',
')':')',
' ':' ',
'&':'&',
'[':'[',
']':']',
'"':'"'
}
def login(): # 登陸函數
print('請輸入你的賬號')
user = input()
print('請輸入你的密碼')
password = input()
# 設置一個cookie處理器,它負責從服務器下載cookie到本地,並且在發送請求時帶上本地的cookie
cj = http.cookiejar.LWPCookieJar()
cookie_support = urllib.request.HTTPCookieProcessor(cj)
opener = urllib.request.build_opener(cookie_support, urllib.request.HTTPHandler)
urllib.request.install_opener(opener)
url = 'http://acm.hit.edu.cn/hoj/system/login' # 登陸的界面
# 這個最好加上,不然由於內部信息默認顯示為機器代理,可能被服務器403 Forbidden拒絕訪問
header={'User-Agent':'Magic Browser'}
# 構造Post數據,從抓大的包里分析得出的或者通過查看網頁源代碼可以得到
data = {
'user' : user, # 你的用戶名
'password' : password, # 你的密碼,密碼可能是明文傳輸也可能是密文,如果是密文需要調用相應的加密算法加密
'submit' : 'Login' # 特有數據,不同網站可能不同
}
data = urllib.parse.urlencode(data).encode('utf-8')
# 發送請求,得到服務器給我們的響應
response = urllib.request.Request(url, data,header)
# 通過urllib提供的request方法來向指定Url發送我們構造的數據,並完成登錄過程
urllib.request.urlopen(response)
return
def solve(html,i):
txt = html.decode('gbk','ignore')
start = txt.find('<span')
if start==-1: # 沒有span,表示此題沒AC
return
p = '.java'
if txt.find('import')==-1:
p = '.cpp'
fout = open('txt_'+str(i)+p,'w')
while True:
end = txt.find('<span',start+5)
if end==-1:
end = txt.find('</span>',start)
x = txt.find('>',start)+1
w = ''
ok = True
while x<end:
if txt[x]=='<':
ok = False
elif txt[x]=='>':
ok = True
if not ok or txt[x]=='>':
x += 1
continue
if txt[x]=='&': # 進行特殊的解碼
t4 = txt[x:x+4]
t5 = txt[x:x+5]
t6 = txt[x:x+6]
if t4 in hashTable:
w += hashTable[t4]
x += 4
elif t5 in hashTable:
w += hashTable[t5]
x += 5
elif t6 in hashTable:
w += hashTable[t6]
x += 6
else:
w += txt[x]
x += 1
else:
w += txt[x]
x += 1
fout.write(w)
if end==start:
break
start = end
fout.close()
return
def run(): # 抓取所有AC代碼
for i in range(1001,3169):
url = 'http://acm.hit.edu.cn/hoj/problem/solution/?problem='+str(i)
response = urllib.request.urlopen(url)
html = response.read()
solve(html,i)
#time.sleep(0.5) # 時間間隔為0.5s發送一次抓取請求,減輕hoj服務器壓力
return
login()
run()
我所有的HOJ AC代碼,在http://pan.baidu.com/s/1gdsX9DL
提交AC代碼:
這個其實跟登陸並抓取AC代碼原理基本一樣。

# 構造Post數據,從網頁數據分析得出
data = {
'Proid' : num,
'Language' : language,
'Source' : txt # 可以通過查看網頁源代碼看出
}
另外,我們需要用到listdir()函數,以此來獲得當前目錄下所有的文件。
這里我們需要設置time.sleep(3) ,間隔三秒遞交一次代碼,減輕服務器壓力,以免使得HOJ服務器崩了。
總的代碼如下:
import urllib
import http.cookiejar
import time
from os import listdir
def login():
print('請輸入你的賬號')
user = input()
print('請輸入你的密碼')
password = input()
cj = http.cookiejar.LWPCookieJar()
cookie_support = urllib.request.HTTPCookieProcessor(cj)
opener = urllib.request.build_opener(cookie_support, urllib.request.HTTPHandler)
urllib.request.install_opener(opener)
url = 'http://acm.hit.edu.cn/hoj/system/login'
header = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
'Referer' : '******'}
# 構造Post數據,他也是從抓大的包里分析得出的。
data = {'op' : 'dmlogin',
'f' : 'st',
'user' : user, # 用戶名
'password' : password, # 密碼,密碼可能是明文傳輸也可能是密文,如果是密文需要調用相應的加密算法加密
'submit' : 'Login' # 特有數據,不同網站可能不同
}
data = urllib.parse.urlencode(data).encode('utf-8')
request = urllib.request.Request(url, data,header)
# 通過urllib2提供的request方法來向指定Url發送我們構造的數據,並完成登錄過程
urllib.request.urlopen(request)
return
def solve(file): # file:為文件名,格式是 txt_problemNumber.cpp或者 txt_problemNumber.java
ed = file.find('.')
language = file[ed+1:len(file)]
if language!='cpp' and language!='java':
return
if language=='cpp':
language = 'C++'
else:
language = 'Java'
st = file.find('_')
num = file[st+1:ed]
url = 'http://acm.hit.edu.cn/hoj/problem/submit/?id='+num
header = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
'Referer' : '******'}
fin = open(file,'rb')
txt = fin.read().decode('gbk','ignore')
fin.close()
# 構造Post數據,從網頁數據分析得出
data = {
'Proid' : num,
'Language' : language,
'Source' : txt # 可以通過查看網頁源代碼看出
}
data = urllib.parse.urlencode(data).encode('utf-8') # 使用UTF-8編碼方式進行編碼
request = urllib.request.Request(url, data,header) # 添加消息頭
# 通過urllib2提供的request方法來向指定Url發送我們構造的數據,並完成登錄過程
urllib.request.urlopen(request)
time.sleep(3) # 間隔三秒遞交一次代碼,減輕服務器壓力
return
def run():
allFile = listdir() # 獲得當前目錄下所有的文件
for file in allFile:
solve(file)
return
login()
run()
codeforces爬AC代碼(有些網頁格式不太一樣,最后直接把那些題目忽略了,囧)(CF python代碼尚未正確,有待改進。。。)
import urllib
user = 'yejinru' # 用戶
page = 21 # 提交的頁數
def solve(html,name):
txt = html.decode('utf-8','ignore')
cpp = '.cpp'
start = txt.find('include')-1
if start==-2:
start = txt.find('public static void Main')
if start==-1:
cpp = '.py'
else:
cpp = '.java'
end = txt.find('</pre>',start+100)
fout = open('cf/'+name+cpp,'wb')
pro = txt[start:end]
pro = pro.replace(u'<',u'<')
pro = pro.replace(u'>',u'>')
pro = pro.replace(u'"',u'"')
pro = pro.replace(u'&',u'&')
fout.write(pro.encode('gbk','ignore'))
fout.close()
return
def fun(html):
txt = html.decode('utf-8','ignore')
pre = 0
while True:
cur = txt.find('<span class=\'verdict-accepted\'>',pre)
if cur==-1:
return
pre = cur+100
p = txt.find( '"',txt.find('submissionId="',cur-50) )+1
pid = ''
while txt[p]!='"':
pid += txt[p]
p += 1
p = txt.find('data-problemId="',cur-300)
if p==-1:
return
p = txt.find('>',txt.find('<a href="/',p))+19
if p==18:
return
tid = ''
while txt[p]>='0' and txt[p]<='9':
tid += txt[p]
p += 1
if len(tid)>3:
continue
url = 'http://codeforces.com/contest/'+tid+'/submission/'+pid
print(url)
response = urllib.request.urlopen(url)
html = response.read()
solve(html,tid+txt[p])
fout = open('cf/'+pid+'.html','wb')
fout.write(html)
fout.close()
return
def run():
add = '/page/'
first = 'http://codeforces.com/submissions/'+user
for i in range(1,page+1): # 這是因為http://codeforces.com/submissions/yejinru這里只有21頁
url = ''
if i==1:
url = first
else:
url = first+add+str(i)
response = urllib.request.urlopen(url)
html = response.read()
fun(html)
return
run()
