这个工具是我暑假的时候使用Python编写的,写它的原因只是为了钻一部分论坛的漏洞来刷积分啦~~最开始只是和朋友分享此代码(一起刷才开心嘛,死也不忘拉个垫背的),不过现在我最初刷积分的论坛也把这个漏洞给补上了,因此还是分享出来以免以后真的把这个好不容易写出来的代码给遗忘掉了233。
许多DZ内核的论坛把大部分的刷积分漏洞都给不上了,但是一般点评漏洞是存在的。不过叫它漏洞也不算漏洞吧,点评和正常的回复其实本质是相同的。但是说它们相同又不是完全相同的,点评是不会把帖子顶到板块的最上面,同时也不计入此帖子的总回复数,但同时又计入到你的总回复数中,并且得到和回复相同的积分数。也就是说点评在论坛的显示系统中是不算回复的,但是在积分系统中和回复有同等地位,隐蔽性高。因为点评的这些特性,导致了点评刷分的存在。只要不是管理员变态到自写管理工具直接查看数据库(我之前刷积分的论坛的管理员就是这么做的!),基本上是不会被发现的。封掉此漏洞也是十分麻烦,需要修改到DZ的源码(我之前刷积分的论坛的管理员还是这么做的!),所以应该说是最安全的刷积分方式吧。
在此代码中,我使用了fc_lamp的“通用登录DZ论坛”函数。此函数只支持最简单的DZ论坛登录,有验证码或者验证回答的就无能为力了。需要的可以使用浏览器提取cookies并插入其中就好了(具体怎么做我也没研究过,靠你们了~)。
使用的时候修改__main__中的dom变量为所要使用的论坛域名就可以测试了。
#-*- coding:utf-8 -*-
import urllib2,urllib,cookielib,re
import getpass
import random
import time
import sqlite3
import sys,locale,codecs
'''
Copyright (c) 2014 Hintay <[email protected]>
DZ论坛Post点评工具
制作:Hintay
参数说明:
dom:网站域名,注意格式必须是:http://www.xxx.xx/(必填),
'''
reload(sys)
sys.setdefaultencoding('utf8')
localeinfo = locale.getdefaultlocale()
type = localeinfo[1]
codecs.register(lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
#VPS
if type == None:
#print u"Maybe you should run 'export LANG=zh_CN.UTF-8' to show Chinese"
type = 'UTF-8'
sys.stdout = codecs.lookup('utf-8')[-1](sys.stdout)
'''
Copyright (c) 2010 Daira Hopwood
Windows API显示UTF-8
来自 http://stackoverflow.com/questions/878972/windows-cmd-encoding-change-causes-python-crash
'''
if sys.platform == "win32":
import codecs
from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int
from ctypes.wintypes import BOOL, HANDLE, DWORD, LPWSTR, LPCWSTR, LPVOID
original_stderr = sys.stderr
# If any exception occurs in this code, we'll probably try to print it on stderr,
# which makes for frustrating debugging if stderr is directed to our wrapper.
# So be paranoid about catching errors and reporting them to original_stderr,
# so that we can at least see them.
def _complain(message):
print >>original_stderr, isinstance(message, str) and message or repr(message)
# Work around <http://bugs.python.org/issue6058>.
codecs.register(lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
# Make Unicode console output work independently of the current code page.
# This also fixes <http://bugs.python.org/issue1602>.
# Credit to Michael Kaplan <http://blogs.msdn.com/b/michkap/archive/2010/04/07/9989346.aspx>
# and TZOmegaTZIOY
# <http://stackoverflow.com/questions/878972/windows-cmd-encoding-change-causes-python-crash/1432462#1432462>.
try:
# <http://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx>
# HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
# returns INVALID_HANDLE_VALUE, NULL, or a valid handle
#
# <http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx>
# DWORD WINAPI GetFileType(DWORD hFile);
#
# <http://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx>
# BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode);
GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32))
STD_OUTPUT_HANDLE = DWORD(-11)
STD_ERROR_HANDLE = DWORD(-12)
GetFileType = WINFUNCTYPE(DWORD, DWORD)(("GetFileType", windll.kernel32))
FILE_TYPE_CHAR = 0x0002
FILE_TYPE_REMOTE = 0x8000
GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD)) \
(("GetConsoleMode", windll.kernel32))
INVALID_HANDLE_VALUE = DWORD(-1).value
def not_a_console(handle):
if handle == INVALID_HANDLE_VALUE or handle is None:
return True
return ((GetFileType(handle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR
or GetConsoleMode(handle, byref(DWORD())) == 0)
old_stdout_fileno = None
old_stderr_fileno = None
if hasattr(sys.stdout, 'fileno'):
old_stdout_fileno = sys.stdout.fileno()
if hasattr(sys.stderr, 'fileno'):
old_stderr_fileno = sys.stderr.fileno()
STDOUT_FILENO = 1
STDERR_FILENO = 2
real_stdout = (old_stdout_fileno == STDOUT_FILENO)
real_stderr = (old_stderr_fileno == STDERR_FILENO)
if real_stdout:
hStdout = GetStdHandle(STD_OUTPUT_HANDLE)
if not_a_console(hStdout):
real_stdout = False
if real_stderr:
hStderr = GetStdHandle(STD_ERROR_HANDLE)
if not_a_console(hStderr):
real_stderr = False
if real_stdout or real_stderr:
# BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars,
# LPDWORD lpCharsWritten, LPVOID lpReserved);
WriteConsoleW = WINFUNCTYPE(BOOL, HANDLE, LPWSTR, DWORD, POINTER(DWORD), \
LPVOID)(("WriteConsoleW", windll.kernel32))
class UnicodeOutput:
def __init__(self, hConsole, stream, fileno, name):
self._hConsole = hConsole
self._stream = stream
self._fileno = fileno
self.closed = False
self.softspace = False
self.mode = 'w'
self.encoding = 'utf-8'
self.name = name
self.flush()
def isatty(self):
return False
def close(self):
# don't really close the handle, that would only cause problems
self.closed = True
def fileno(self):
return self._fileno
def flush(self):
if self._hConsole is None:
try:
self._stream.flush()
except Exception, e:
_complain("%s.flush: %r from %r"
% (self.name, e, self._stream))
raise
def write(self, text):
try:
if self._hConsole is None:
if isinstance(text, unicode):
text = text.encode('utf-8')
self._stream.write(text)
else:
if not isinstance(text, unicode):
text = str(text).decode('utf-8')
remaining = len(text)
while remaining > 0:
n = DWORD(0)
# There is a shorter-than-documented limitation on the
# length of the string passed to WriteConsoleW (see
# <http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1232>.
retval = WriteConsoleW(self._hConsole, text,
min(remaining, 10000),
byref(n), None)
if retval == 0 or n.value == 0:
raise IOError("WriteConsoleW returned %r, n.value = %r"
% (retval, n.value))
remaining -= n.value
if remaining == 0: break
text = text[n.value:]
except Exception, e:
_complain("%s.write: %r" % (self.name, e))
raise
def writelines(self, lines):
try:
for line in lines:
self.write(line)
except Exception, e:
_complain("%s.writelines: %r" % (self.name, e))
raise
if real_stdout:
sys.stdout = UnicodeOutput(hStdout, None, STDOUT_FILENO,
'<Unicode console stdout>')
else:
sys.stdout = UnicodeOutput(None, sys.stdout, old_stdout_fileno,
'<Unicode redirected stdout>')
if real_stderr:
sys.stderr = UnicodeOutput(hStderr, None, STDERR_FILENO,
'<Unicode console stderr>')
else:
sys.stderr = UnicodeOutput(None, sys.stderr, old_stderr_fileno,
'<Unicode redirected stderr>')
except Exception, e:
_complain("exception %r while fixing up sys.stdout and sys.stderr" % (e,))
# While we're at it, let's unmangle the command-line arguments:
# This works around <http://bugs.python.org/issue2128>.
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int)) \
(("CommandLineToArgvW", windll.shell32))
argc = c_int(0)
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
argv = [argv_unicode[i].encode('utf-8') for i in xrange(0, argc.value)]
if not hasattr(sys, 'frozen'):
# If this is an executable produced by py2exe or bbfreeze, then it will
# have been invoked directly. Otherwise, unicode_argv[0] is the Python
# interpreter, so skip that.
argv = argv[1:]
# Also skip option arguments to the Python interpreter.
while len(argv) > 0:
arg = argv[0]
if not arg.startswith(u"-") or arg == u"-":
break
argv = argv[1:]
if arg == u'-m':
# sys.argv[0] should really be the absolute path of the module source,
# but never mind
break
if arg == u'-c':
argv[0] = u'-c'
break
# if you like:
sys.argv = argv
#数据库设置
conn = sqlite3.connect('data.db')
cur = conn.cursor()
#数据库初始化函数
def InitDB():
sql = '''create table if not exists post (
tid text,
pid text,
replied integer)'''
cur.execute(sql)
conn.commit()
def sql_check(tid, pid):
#数据库检测
sql = "select * from post where tid='%s' and pid='%s'" % (tid, pid)
cur.execute(sql)
#如果没有即新建一行
if len(cur.fetchall()) == 0:
sql = "insert into post values ('%s', '%s', '%d')" % (tid, pid, 0)
cur.execute(sql)
conn.commit()
def sql_replied(tid, pid):
sql = "select replied from post where tid='%s' and pid='%s'" % (tid, pid)
cur.execute(sql)
replied = cur.fetchone()
replied = replied[0]
return replied
'''
Copyright (c) 2011 fc_lamp
Copyright (c) 2014 Hintay <[email protected]>
Original Author:
fc_lamp
Changes Statement:
Changes made by Hintay <[email protected]>
通用登陆DZ论坛
参数说明parms:
username:用户名(必填),
password :密码(必填),
domain:网站域名,注意格式必须是:http://www.xxx.xx/(必填),
answer:问题答案,
questionid:问题ID,
referer:跳转地址
这里使用了可变关键字参数(相关信息可参考手册)
'''
def login_dz(**parms):
#初始化
parms_key = ['domain','answer','password','questionid','referer','username']
arg = {}
for key in parms_key:
if key in parms:
arg[key] = parms[key]
else:
arg[key] = ''
#cookie设置
cookieFile = './kan_cookies.dat'
cookie = cookielib.LWPCookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
urllib2.install_opener(opener)
#获取formhash
pre_login = arg['domain']+'member.php?mod=logging&action=login&infloat=yes&handlekey=login&inajax=1&ajaxtarget=fwin_content_login'
c = opener.open(pre_login).read()
cookie.save(cookieFile)
patt = re.compile(r'.*?name="formhash".*?value="(.*?)".*?')
formhash = patt.search(c)
if not formhash:
raise Exception('GET formhash Fail!')
formhash = formhash.group(1)
#登陆
postdata = {
'answer':arg['answer'],
'formhash':formhash,
'password':arg['password'],
'questionid':0 if arg['questionid']=='' else arg['questionid'],
'referer':arg['domain'] if arg['referer']=='' else arg['referer'],
'username':arg['username'],
}
postdata = urllib.urlencode(postdata)
req = urllib2.Request(
url= arg['domain']+'member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash=LCaB3&inajax=1',
data=postdata
)
c = opener.open(req).read(300)
flag = u'登陆失败 %s'%arg['username']
#调试
#print c
if 'succeedhandle_login' in c:
flag = True
return flag
def thread_pages(domain,tid,uid):
#获取页数函数
pre_page = domain+'forum.php?mod=viewthread&tid=%s&page=1&authorid=%s' % (tid,uid)
c = urllib2.urlopen(pre_page).read()
c = c.decode('utf8')
patt = re.compile(u'.*?title="\u5171\s*(.*?)\s*\u9875".*?')
pages = patt.search(c)
if not pages:
return '1'
pages = pages.group(1)
return pages
def GetPids(domain,tid,uid,page):
#获取pid列表函数
tidurl = domain+'forum.php?mod=viewthread&tid=%s&page=%s&authorid=%s' % (tid,page,uid)
response = urllib2.urlopen(tidurl)
content = response.read()
patt = '.*?<table\s*id="pid(.*?)".*?'
pids = re.findall(patt, content)
return pids
def PostComment(domain, tid, pid, content):
#post点评函数
commenturl = domain+'forum.php?mod=misc&action=comment&tid=%s&pid=%s&extra=&page=1&infloat=yes&handlekey=comment&inajax=1&ajaxtarget=fwin_content_comment' % (tid,pid)
commentsubmiturl = domain+'forum.php?mod=post&action=reply&comment=yes&tid=%s&pid=%s&extra=&page=1&commentsubmit=yes&infloat=yes&inajax=1' % (tid,pid)
#调试
#print commenturl
#print commentsubmiturl
#获取formhash
while True:
try:
c = urllib2.urlopen(commenturl).read()
break
except Exception,e:
print u'错误:%s'%e
print u'正在重试.......'
#调试
#print c
patt = re.compile(r'.*?name="formhash".*?value="(.*?)".*?')
formhash = patt.search(c)
if not formhash:
raise Exception('GET formhash Fail!')
formhash = formhash.group(1)
#点评数据
#content = content.decode(type)
#content = content.encode('utf8')
postdata = {
'formhash':formhash,
'message': content,
'handlekey': '',
'commentsubmit':'true',
}
postdata = urllib.urlencode(postdata)
#调试
#print postdata
req = urllib2.Request(
url=commentsubmiturl,
data=postdata
)
while True:
try:
c = urllib2.urlopen(req).read(300)
break
except Exception,e:
print u'错误:%s'%e
print u'正在重试.......'
#调试
#print c.decode('utf8')
c = c.decode('utf-8')
if 'errorhandle_' in c:
print u'[%s] 点评失败!' % commenturl
patt = re.compile(".*?errorhandle_\('(.*?)',.*?")
error = patt.search(c).group(1)
print u'错误信息:%s'%error
one = sql_replied(tid, pid)
page = commentmore_check(dom, tid, pid)
print u'已点评数:%s' % one
print u'本层回复总点评页数:%s' % page
print u'--------------------'
else:
replied = sql_replied(tid, pid)
replied = replied+1
#更新数据库
sql = "update post set replied=%s where tid='%s' and pid='%s'" % (replied, tid, pid)
cur.execute(sql)
conn.commit()
one = sql_replied(tid, pid)
page = commentmore_check(dom, tid, pid)
print u'[%s] 点评成功' % commenturl
print u'已点评数:%s' % one
print u'本层回复总点评页数:%s' % page
print u'--------------------'
#url正则匹配函数
def url_regex():
#输入错误的网址则再次输入
while True:
url = raw_input(unicode('请输入地址(如果为空则手动选择模式及参数):','utf-8')).strip()
#若输入为空则返回空
if url=='':
urls = 0
mod = 0
return urls,mod
#判断网址模式的格式
pre_patt = re.compile('.*?mod=(.*?)&')
mod = pre_patt.search(url)
if mod != None:
mod = mod.group(1)
if mod == 'misc' or mod == 'viewthread':
break
print u'网址模式错误,您是不是搞混了网址呢?'
else:
print u'网址输入错误,请重新输入'
if mod == 'misc':
pattd = re.compile('.*?/forum\.php\?mod=misc&action=comment&tid=(\d*)&pid=(\d*)')#点评
urls = pattd.search(url)
if mod == 'viewthread':
pattz = re.compile('.*?/forum\.php\?mod=viewthread&tid=(\d*)&page=\d*&authorid=(\d*)')#只看作者
urls = pattz.search(url)
return urls,mod
#只允许数字输入函数
def input_number(msg):
r = raw_input(unicode(msg,'utf-8')).strip()
while True:
if r.isdigit():
break
r = raw_input(unicode('您输入的不是数字,请重新输入:','utf-8')).strip()
return r
#无输入返回默认值函数
def get(msg,default=0):
r = raw_input(unicode(msg,'utf-8')).strip()
while True:
if r.isdigit():
break
if r=='':
return default
break
r = raw_input(unicode('您输入的不是数字,请重新输入:','utf-8')).strip()
return r
#点评页数检查
def commentmore_check(domain, tid, pid):
commentmoreurl = domain+'forum.php?mod=misc&action=commentmore&tid=%s&pid=%s&page=1&inajax=1&ajaxtarget=comment' % (tid,pid)
c = urllib2.urlopen(commentmoreurl).read()
patt = re.compile('.*?comment"\s*>\.\.\.\s*(.*?)</a>')
dotpages = patt.search(c)
if dotpages == None:
patt = re.compile('.*?"comment"\s*>(\d*)</a>')
pages = patt.findall(c)
if len(pages) == 0:
page = '1'
else:
page = pages[-1]
else:
page = dotpages.group(1)
return page
#点评循环
def postloop(dom, tid, pid):
#随机评论列表,可自行添加
commentlist = [
u'好人一生平安~~',
u'多谢分享,顶一下',
u'说的不错~~~',
u'你太油菜了!',
u'有的人认为坚持会让我们变得更强大,但有时候放手也会。',
u'为了爱,失恋是必要的;为了光明,黑暗是必要的。',
u'忘掉岁月,忘掉痛苦,忘掉你的坏,我们永不永不说再见。',
u'当一个人最看重的东西是面子,那他为此失去的一定很多。',
u'世界上有两个我,一个假装快乐,一个真心难过。',
u'我要的,不是短暂的温柔,而是一生的守候。',
u'请把你的心给我,与我为伍,这个世界太残酷了,我有些害怕。',
u'没有一百分的另一半,只有五十分的两个人。',
u'命中有很多事情足以把你打倒,但真正能把你打倒的是你的心态。',
u'你喜欢的人也喜欢你,你想念的人也正在想念你。这就是全世界最重要的事情,拿什么都不能换。',
u'不知不觉,是这世上最可怕的力量。',
u'温暖在你心里,只是你自己还没有发现它。',
u'当我们感到幸福的时候,何必去想这幸福是永恒的,还是暂时的。忧虑,是幸福最大的敌人。',
u'给自己的三句话:一、年轻,什么都还来得及;二、不要纠缠于小事;三、你现在遇到的事都是小事。',
u'“归属感”是你强烈地想和他在一起,“安全感”是你觉得他强烈地想和你在一起。',
u'每棵大树,都曾只是一粒种子。',
u'有时候你必须跳出“窗外”,然后在坠落的过程中长出“翅膀”。',
u'伤心最大的建设性,在于明白,那颗心还在老地方。',
u'深的话要浅浅地说,长长的路要挥霍地走。大大的世界要率真地感受,会痛的伤口要轻轻地揉。',
u'爱情里最忌讳的就是:两人都幻想着彼此的未来,却也总惦记着对方的过去。',
u'即使不见面,不说话,不发信息,我也会在心里留一个位置,安安稳稳的放着一个人。',
u'男生没有主动找女生,那是说明他是真的不想理她了。女生没有主动找男生,是因为在等他找她。',
u'我相信,爱可以排除万难。只是,万难之后,又有万难,这是我更相信的。',
u'第一次的爱,始终无法轻描淡写。我对你,只有放弃,没有忘记。',
u'如果觉得生活是一种刁难,一开始就输了。如果觉得刁难是一种雕刻,迟早都会赢的。',
u'你若想要得到,就别只是期望。人生短暂,经不起等待。'
]
print u'--------------------'
print u'开始点评...'
sql_check(tid, pid)
while True:
#循环判断
if pageoption == '1':
#页数
page = commentmore_check(dom, tid, pid)
page = int(page)-1
if page >= setpage:
print u'点评页数已到您设定的%d页' % setpage
break
else:
#次数
replied = sql_replied(tid, pid)
if replied >= count:
print u'点评次数已到您设定的%d次' % replied
break
time.sleep(sleep)
content = random.choice(commentlist)
print u'随机选择的点评内容:%s' % content
print u'pid:%s'%pid
PostComment(dom, tid, pid, content)
if __name__ == '__main__':
#网站域名,注意格式必须是:http://www.xxx.xx/(必填)
dom='http://www.sample.com/'
print u'-----------------------------------------'
print u'欢迎使用由Hintay编写的DZ论坛Post点评工具'
print u' 版本:0.7'
print u' 更新时间:2014/7/24 21:56'
print u''
print u' Copyright [email protected]'
print u'-----------------------------------------'
print u'当前使用的域名:%s' % dom
print u'-----------------------------------------'
InitDB()
try:
while True:
user = raw_input(unicode('用户名:','utf-8')).strip()
if sys.stdin.encoding == 'cp65001':
user = user.decode(type).decode('utf-8').encode('utf-8')
else:
user = user.decode(type).decode(type).encode('utf-8')
pwd = getpass.getpass().strip()
print u'登录中...'
#登录论坛(部分需要验证码的论坛可以自行修改使用cookies)
flag = login_dz(username=user,password=pwd,domain=dom)
if flag == True:
#print(flag)
print u'登录成功...'
break
print u'登录失败,请重新登录'
print u'--------------------'
print u'--------------------'
#网址处理
pre_urls = url_regex()
urls = pre_urls[0]
mod = pre_urls[1]
if mod == 'misc':
option = '1'
if mod == 'viewthread':
option = '2'
if urls == 0:
print u'您没有输入网址,请手动选择模式'
print u'--------------------'
print u'请选择您要使用的模式'
print u'1.指定tid与pid(单层模式)'
print u'2.指定tid与uid(自动模式)'
while True:
option = raw_input(unicode('请输入您的选项:','utf-8')).strip()
if option == '1' or option == '2':
break
print u'输入错误,请重新输入'
if option == '1':
if urls == 0:
tid = input_number('请输入您要点评的帖子tid:')
pid = input_number('请输入您要点评的楼层pid:')
else:
tid = urls.group(1)
pid = urls.group(2)
else:
if urls == 0:
tid = input_number('请输入您要点评的帖子tid:')
uid = input_number('请输入您要点评的用户uid:')
else:
tid = urls.group(1)
uid = urls.group(2)
print u'--------------------'
print u'请选择您要使用的点评模式'
print u'1.设定点评的页数'
print u'2.设定点评的次数'
while True:
pageoption = get('请输入您的选项(默认为1):','1')
if pageoption == '1' or pageoption == '2':
break
print u'输入错误,请重新输入'
#根据不同情况显示选项
if pageoption == '1':
print u'您的选项:页数模式'
print u'--------------------'
print u'注意:实际点评到设定的点评页数+1才会停止(即点评满设定页数)'
setpage = get('请输入要点评的页数(默认为30页):',30)
print u'点评页数:%s' % setpage
setpage = int(setpage)
else:
print u'您的选项:次数模式'
print u'--------------------'
count = get('请输入要点评的次数(默认为150次):',150)
print u'点评次数:%s' % count
count = int(count)
sleep = get('请输入每次点评间隔秒数(默认为5秒):',5)
print u'间隔秒数:%s秒' % sleep
sleep = int(sleep)
if option == '1':
postloop(dom, tid, pid)
else:
pages = thread_pages(dom,tid,uid)
pages = int(pages)
for page in range(0,pages):
page = page+1
pids = GetPids(dom, tid, uid, page)
for pid in pids:
if pid == '1':
print u'不能点评楼主!已跳过'
else:
postloop(dom, tid, pid)
print u'--------------------'
print u'点评完毕'
print u'执行完毕'
except Exception,e:
print u'错误:%s'%e
我用了抓包的方式获取了post路径和参数,使用了许多ajax返回的数据进行匹配,实现了最快和节省流量的post点评。想最初有刷积分这个想法的时候我还是用按键精灵实现的,我都没想到后来我能用Python把这个想法写成这样,我自己都好佩服我自己啊。
另一个小秘密:我不仅没学过PHP,我也没学过Python,这是真实的故事~~
刷 http://www.re 啊