每次都只能在家里的电脑才能更新博客,太不方便了,如果能在浏览器上动态更新就好了。但是这样需要云服务器,,,没钱。于是想了个逆天的方案:
- 在某博客网站上更新文章。
- 在家里电脑设定程序定期爬取该网站的文章。
- 更新。
wa wa wa,太完美了吧。
于是目标变成:爬取私有文章。
于是就有了个前提:登录账号。
一种简单的方式就是手动获取cookie,然后放进请求头里就可以了。但是这样不优雅,还要定期手动重新获取。
第二种方案,就是模拟登录获取cookie。我选择这一种。
首先可以用 F12 发现洛谷登录请求的流程:
- 请求
https://www.luogu.com.cn/auth/login(GET) ,似乎没什么用。 - 在输入用户名后,请求
https://www.luogu.com.cn/auth/login-methods?login=username(GET)。没看到返回的是啥。 - 提交登录,
https://www.luogu.com.cn/do-auth/password(POST),要用户名密码和验证码。
另外有趣的是,洛谷反爬机制挺强的,第一会通过 cookie 验证,错误是返回 set_cookie,然后设置成这个就行了,挺搞笑的。
另一个是在请求登录时,发现请求头会多出两个:
- X-Requested-With : XMLHttpRequest,听说是用来表明是 AJAX 异步接口请求的。
- X-CSRF-TOKEN : …,这个就麻烦了,查询了资料。
找了半天都没找到这玩意在哪生成的,幸好有前人指路。首先应该不是在 URL 或请求头中,我没看到。
哦!,就在网页的 html 里:<meta name="csrf-token" content="1774766584:XvhhVwI22HPNHDEKIAzL5n9j3EERx5qCFt5GGZrhafw=">
“
?
$ python .\luogu.pyHi agent, as you see, I am just a cute teapot. Wanna a cup of tea?问题不大,改下 headers 就行了。
import reresponse = requests.get(HOMEPAGE , headers = HEADERS)csrf_token = re.search(r'<meta name="csrf-token" content="([^"]+)"', response.text).group(1)print(csrf_token)嗯。
然后按照流程模拟就行了:

最难的应该就结束了,然后扩展的话可以加一个验证码识别。这部分先搁一下,先把爬取文章实现了。
这里找到一个洛谷 API 的 文档:
按照上面的说明,编写代码,并自动更新文件信息。
def get_blogs_list(session : requests.Session , user , page): #670766 API = "https://www.luogu.com.cn/api/blog/userBlogs" blog_list = session.get(API , headers = HEADERS , params = {"user" : str(user) , "page" : page}) return blog_list.json()
def pull_blog_md(session : requests.Session , id , path): API = "https://www.luogu.com.cn/api/blog/detail/" ret = session.get(API + id , headers = HEADERS).json()["data"] title = ret["Title"][4:] post_time = strftime("%Y-%m-%d" , localtime(ret["PostTime"])) content = ret["Content"] _ = re.search(r"\A```[\s\S]*?```" , content) print(f"拉取 {title}") if _ == None: print(f"{title} 格式错误!") return False information = _.group() information = information[3:(len(information) - 3)] other = content[_.end():] with open(f"{path}\\{title}.md" , "w" , encoding = "utf-8") as f: f.write("---") f.write(information) f.write(f"published: {post_time}\npubDate: {post_time}\ndate: {post_time}\ntitle: {title}\n---") f.write(other) print(f"拉取 {title} 成功!") return True
def pull_context(session : requests.Session , user_id , path): now_page = 1 while True: blog_list = get_blogs_list(session , user_id , now_page)["blogs"]["result"] if len(blog_list) == 0: break for i in blog_list: if i["title"][:4] == "blog": pull_blog_md(session , str(i["id"]) , path) now_page += 1下一步可以把,拉取下来的存到 blog 的文件夹,在调用我之前写的更新程序,自动上传到 github。
下面是我在洛谷上创建的测试文章:

爬虫可以爬取创建时间等,这部分我让程序自动帮我填写。剩下的我就照常写在文章开头。
下面是爬取下来后的效果:

嗯,挺好的。
然后调用更新:
$ python .\pull_from_luogu.pyCookie 已加载Cookie 有效性: True当前 Cookie: {'__client_id': '不给你看', '_uid': '670766'}拉取 "测试测试"拉取 "测试测试" 成功!$ python .\update_post.py更新 "测试测试"git ok.效果你们应该已经看到了。。
剩下的还有一个问题,就是版本问题: 什么叫版本问题呢,就是目前我的文章应该有三个端:github,本地,还有洛谷。每次修改是要以哪个为准呢? 嗯,懒得搞了,反正就我自己用。。
就这样吧。
最后放下代码吧:
import requests , re , base64 , jsonfrom time import localtime , strftimefrom os import mkdirfrom os.path import exists
USERID = 670766SAVE_PATH = r"D:\\notes\\blog\\"
HOMEPAGE = "https://www.luogu.com.cn/auth/login"CAPTCHA = "https://www.luogu.com.cn/lg4/captcha"LOGIN = "https://www.luogu.com.cn/do-auth/password" #POSTLOGIN_METHODS = "https://www.luogu.com.cn/auth/login-methods"
COOKIE_FILE = "luogu_cookie.json"PASSWORD_FILE = "luogu_password.txt"
HEADERS = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0" , "Referer" : "https://www.luogu.com.cn/" , "Origin" : "https://www.luogu.com.cn" , "X-Requested-With" : "XMLHttpRequest" , # AJAX 异步接口请求}
def save_cookie(session: requests.Session): with open(COOKIE_FILE , "w" , encoding = "utf-8") as f: json.dump(requests.utils.dict_from_cookiejar(session.cookies) , f , indent = 2) print("Cookie 已保存")
def load_cookie(session: requests.Session): with open(COOKIE_FILE , "r", encoding = "utf-8") as f: session.cookies.update(json.loads(f.read())) print("Cookie 已加载")
def check_cookie_valid(session: requests.Session): check_url = "https://www.luogu.com.cn/user/setting" resp = session.get(check_url, headers = HEADERS , allow_redirects=False) valid = resp.status_code == 200 print(f"Cookie 有效性: {'True' if valid else 'False'}") return valid
def login(session: requests.Session): LG_HEADERS = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0" , "Referer" : "https://www.luogu.com.cn/" , "X-Requested-With" : "XMLHttpRequest" , "Host" : "www.luogu.com.cn" , }
response = session.get(HOMEPAGE , headers = LG_HEADERS , allow_redirects = True) csrf_token = re.search(r'<meta name="csrf-token" content="([^"]+)"', response.text).group(1)
LG_HEADERS["X-CSRF-TOKEN"] = csrf_token
with open(PASSWORD_FILE , "r") as f: USERNAME = base64.b64decode(f.readline()).decode() PASSWORD = base64.b64decode(f.readline()).decode()
LG_HEADERS["Referer"] = "https://www.luogu.com.cn/auth/login" login1 = session.get(LOGIN_METHODS , params = {"login" : USERNAME} , headers = LG_HEADERS) print("状态码:" , login1.status_code)
while True: captcha_image = session.get(CAPTCHA , headers = LG_HEADERS , allow_redirects = False).content with open("luogu_captcha.jpeg" , "wb") as f: f.write(captcha_image) f.flush()
captcha = input("请查看并输入验证码:")[:4] print(captcha , len(captcha))
data = { "captcha" : captcha , "password" : PASSWORD , "username" : USERNAME }
# HEADERS["Content-Type"] = "application/json" LG_HEADERS["Origin"] = "https://www.luogu.com.cn" login2 = session.post(LOGIN , headers = LG_HEADERS , data = data , allow_redirects = False) print("状态码:" , login2.status_code)
if login2.status_code == 200: print("\033[32m登录成功\033[0m") save_cookie(session) return True elif login2.status_code == 400: print("\033[31m登录失败\003[0m,验证码错误:") elif login2.status_code == 401: print("\033[31m登录失败\033[0m,密码错误:") else: print("\033[31m登录失败\033[0m,其他错误:") print(login2.text)
def get_blogs_list(session : requests.Session , user , page): #670766 API = "https://www.luogu.com.cn/api/blog/userBlogs" blog_list = session.get(API , headers = HEADERS , params = {"user" : str(user) , "page" : page}) return blog_list.json()
def pull_blog_md(session : requests.Session , id , path): API = "https://www.luogu.com.cn/api/blog/detail/" ret = session.get(API + id , headers = HEADERS).json()["data"] title = ret["Title"][5:] post_time = strftime("%Y-%m-%d" , localtime(ret["PostTime"])) content = ret["Content"] _ = re.search(r"\A```[\s\S]*?```" , content) if _ == None: print(f"\033[31m格式错误!\033[0m: 跳过文章 \"{title}\"") return False information = _.group() information = information[3:(len(information) - 3)] other = content[_.end():]
if not exists(f"{path}\\{title}"): mkdir(f"{path}\\{title}") with open(f"{path}\\{title}\\index.md" , "w" , encoding = "utf-8") as f: f.write("---") f.write(information) f.write(f"published: {post_time}\npubDate: {post_time}\ndate: {post_time}\ntitle: {title}\n---") f.write(other) print(f"\033[32m拉取成功!\033[0m:\"{title}\" 已更新!") return True
def pull_context(session : requests.Session , user_id , path): now_page = 1 while True: blog_list = get_blogs_list(session , user_id , now_page)["blogs"]["result"] if len(blog_list) == 0: break for i in blog_list: if i["title"][:4] == "blog": pull_blog_md(session , str(i["id"]) , path) now_page += 1
if __name__ == "__main__": session = requests.Session() load_cookie(session) if not check_cookie_valid(session): session.cookies.clear() login(session) print("当前 Cookie: " , requests.utils.dict_from_cookiejar(session.cookies)) print("开始拉取:\n") pull_context(session , USERID , SAVE_PATH)部分信息可能已经过时