最近看论坛比较多,想提高在论坛的等级,就寻思着写个每天自动刷分的脚本。下面我们就从零开始用python实现一个自动登录,自动访问空间的脚本。我们就以https://www.hostloc.com/作为我们的实验对象。

环境要求

我们需要一个python3的执行环境,还有python包管理器pip,针对实现整个功能我们需要两个等三方的包urllib3和BeautifulSoup4。

# pip 不是环境变量

meshell@python# python3 -m pip install urllib3 BeautifulSoup4

# pip 是环境变量

meshell@python# pip install urllib3 BeautifulSoup4

基础定义

我们需要定义一个简单的类, 有username, password, userAgent, host, identity_cookie_name, cookies的一些属性。

我们把username, password, host, identity_cookie_name作为构造参数传入。

class HostLoc(object):
username = None # 登录用户名
password = None # 登录密码
userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0" # 发送请求的userAgent头
cookies = {} # 记录所有请求的cookie,定义成map
identity_cookie_name = None # 记录登录成功的cookie名称
host = None # 网址host
def __init__(self, username, password, host, identity_cookie_name):
self.username = username
self.password = password
self.host = host
self.identity_cookie_name = identity_cookie_name

实现登录

在实现登录之前需要了解下urllib3的使用,通过这个库来发送http请求官方文档。为类实现一个公共的发送http方法,因为我们对同一个站点发送请求,基本cookie和header每次都是一样的。

...
host = None
def __init__(self, username, password, host, identity_cookie_name):
...
self.http = urllib3.PoolManager(cert_reqs=ssl.CERT_NONE, assert_hostname=False)
...
def _request(self, method, url, fields = None):
headers = {
"origin": self.host,
"referer": self.referer,
"User-Agent": self.userAgent,
}
if len(self.cookies) > 0:
headers['cookie'] = self.joinCookies()
response = self.http.request(method, url, fields, headers)
cookies = self.parseCookie(response.getheader('Set-Cookie'))
if len(cookies) > 0:
self.cookies.update(cookies)
return response
...

Note: 如果你想要去掉urllib3的https验证,你必须设置cert_reqs=ssl.CERT_NONE, assert_hostname=False这两个属性

因为urllib3头都是字符串形式,我们的cookies是定义成map形式,我们需要实现一个方法为它转换成cookie头形式。

def joinCookies(self):
cookie_string = ""
for key, value in self.cookies.items():
cookie_string += key + "=" + value + ";"
return cookie_string.rstrip(";")

解析Cookie是整个请求的中最重要的一步,当登录成功的时候需要记录所有服务端发送的cookie, 下请求下一次页面是需要把这些cookie发送给服务端。看上面的请求方法,我们是通过一个parseCookie的方法来解析cookie,来看看它是怎么实现的。

def parseCookie(self, cookie = None):
cookies = {}
if cookie == None:
return cookies
for value in cookie.split(";"):
hash = value.split("=")
if len(hash) < 2 or hash[0].strip(" ") in ["expires", "Max-Age", "path"]:
continue
name = hash[0]
index = hash[0].find(',')
if index != -1:
name = name[index+1:].lstrip(" ")
cookies[name] = hash[1]
return cookies

上面的代码只解析了cookie的名字和值, 不需要过期时间和路径这些。

登录的实现现有的代码里只需要发送请求,判断登录成功的cookie有没有即可。

...
loginFromUrl = "/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes&inajax=1"
def login(self):
response = self._request("post", self.loginFromUrl, fields={
"fastloginfield": "username",
"username": self.username,
"password": self.password,
"quickforward": "yes",
"handlekey": "ls"
})
if response.status == 400:
print("服务器已限制")
return False
if self.identity_cookie_name in self.cookies:
print("登录成功")
return True
return False

用户信息

在成功登录之后就可以获取当前用户的积分,威望,金钱等信息。使用BeautifulSoup4库来匹配页面的html元素,它就像javascript的jQuery库一样获取DOM,就连API也非常相似,你可以官方文档来查看基本的使用。定义一个方法来打印当前的用户信息。随便定义一个主题页面作为访问入口。

referer = "/forum-45-1.html"
creditUrl = "/home.php?mod=spacecp&ac=credit&showcredit=1&inajax=1&ajaxtarget=extcreditmenu_menu"
def __init__(self, username, password, host, identity_cookie_name):
...
self.referer = self.host + self.referer
self.creditUrl = self.host + self.creditUrl
...
def info(self):
response = self._request(
"post",
self.referer
)
bs = BeautifulSoup(response.data, "lxml")
score = bs.find("a", id="extcreditmenu").string
name = bs.find("strong", class_="vwmy").string
menu_response = self._request("get", self.creditUrl)
menu_response_bs = BeautifulSoup(menu_response.data, "lxml")
hcredit_1 = menu_response_bs.find("span", id="hcredit_1").string
hcredit_2 = menu_response_bs.find("span", id="hcredit_2").string
print("昵称: %s, %s\n威望: %s, 金钱: %s" % (name, score, hcredit_1, hcredit_2))


上图的内容就是通过BeautifulSoup4获取出来的,我们不必自己写正则来获取。我们只使用了一个find来获取指定的内容,此函数也只会返回匹配的一条元素。

积分

在discuz!中获取积分有多很种方式,访问他人主页、发表帖子、回复帖子、每日登录等等都可以获取促积分。我们只实现其中最简单一个访问他人主页,访问他人主页会自动加上积分。

def visitProfile(self, url):
response = self._request("get", url)
print(url + "\n")
bs = BeautifulSoup(response.data, "lxml")
self.visit_loop += 1
all = bs.find_all("a", class_="avt")
visit_len = len(all)
print(visit_len)
if visit_len > 1 and self.visit_loop < 20:
index = random.randint(2, visit_len - 1)
self.visitProfile(self.host + "/" + all[index]['href'])

我们将此方法加入到获取信息方法中,以信息页面中第一个用户作为访问入口,之后通过他最近访问的人随机一个作为访问入口,20次作为他的访问上限。

def info(self):
...
first = self.host + "/" + bs.find("a", class_="notabs")["href"]
print("昵称: %s, %s\n威望: %s, 金钱: %s" % (name, score, hcredit_1, hcredit_2))
self.visitProfile(first)

到此为止,基本流程已经走通。现在需要将获取的cookie写入到文件中,以保证下次执行不需要再次执行登录操作。我们将写入操作放在类的析构阶段,同时也需要在构造函数中读取cookie。

def __init__(self, username, password, host, identity_cookie_name):
...
self.open_file = open('./cookie.txt', 'w')
self.origin_cookies = self.readCookie()
if self.origin_cookies != None:
for value in self.origin_cookies.split(";"):
hash = value.split("=")
self.cookies[hash[0]] = hash[1]
def writeCookie(self, cookie):
self.open_file.write(cookie)
self.open_file.close()
def readCookie(self):
if not pathlib.Path("./cookie.txt").exists():
return None
with open('cookie.txt', 'r') as f:
return f.read()
def __del__(self):
if len(self.cookies) > 0:
self.writeCookie(self.joinCookies())

你可能会问为什么在构造函数里面去打开文件,而不是在writeCookie里面去执行open操作。因为python在__del__是不可以执行open操作的。我们已完成了整个操作。