786 字
2 分钟
记一次pyhon爬虫——以洛谷为例
2026-03-28

每次都只能在家里的电脑才能更新博客,太不方便了,如果能在浏览器上动态更新就好了。但是这样需要云服务器,,,没钱。于是想了个逆天的方案:

  • 在某博客网站上更新文章。
  • 在家里电脑设定程序定期爬取该网站的文章。
  • 更新。

wa wa wa,太完美了吧。

于是目标变成:爬取私有文章。

于是就有了个前提:登录账号。

一种简单的方式就是手动获取cookie,然后放进请求头里就可以了。但是这样不优雅,还要定期手动重新获取。

第二种方案,就是模拟登录获取cookie。我选择这一种。

首先可以用 F12 发现洛谷登录请求的流程:

  1. 请求 https://www.luogu.com.cn/auth/login(GET) ,似乎没什么用。
  2. 在输入用户名后,请求 https://www.luogu.com.cn/auth/login-methods?login=username(GET)。没看到返回的是啥。
  3. 提交登录,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.py
Hi agent, as you see, I am just a cute teapot. Wanna a cup of tea?

问题不大,改下 headers 就行了。

import re
response = 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.py
Cookie 已加载
Cookie 有效性: True
当前 Cookie: {'__client_id': '不给你看', '_uid': '670766'}
拉取 "测试测试"
拉取 "测试测试" 成功!
$ python .\update_post.py
更新 "测试测试"
git ok.

效果你们应该已经看到了。。

剩下的还有一个问题,就是版本问题: 什么叫版本问题呢,就是目前我的文章应该有三个端:github,本地,还有洛谷。每次修改是要以哪个为准呢? 嗯,懒得搞了,反正就我自己用。。

就这样吧。

最后放下代码吧:

import requests , re , base64 , json
from time import localtime , strftime
from os import mkdir
from os.path import exists
USERID = 670766
SAVE_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" #POST
LOGIN_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)
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时