最近上课也上到爬虫了,我想起了 @GamerNoTitle 做过一个爬一言的BLOG:Hitokoto-Spider 一言库爬虫开发日记 (据说这是他的第一个Python实战)

于是我觉得我的第一个实战也可以来搞一个(当然抄代码是不可能的)

参考了一下一言的官方开发者文档,我就敲代码了

项目地址:https://github.com/2X-ercha/Hitokoto-Spider

利用一言官方API爬取

文档中接口说明如下:

官方接口

请求地址

因为有先看了大佬的博客,知道爬下来是个json(这玩意比html好解析多了)

所以我看了看官方json的说明:

参数说明

有些信息是我不需要的,我就不管他了

保留一下信息:"id", "sort", "hitokoto", "from", "from_who", "creator", "created_at"

好了,我们开始爬了

利用requests库爬取数据

def Hitokoto_spider():
ids=np.zeros(10000,dtype=bool)
res=r.get("https://v1.hitokoto.cn",timeout=60)

data=res.json()
if not ids[data["id"]]:
print("{}:\t{}".format(data["id"],data["hitokoto"])) # 输出爬取内容
ids[data["id"]]=True

用ids数组来判断是否抓取过(因为我知道一言的总数不多,数组大小我就只设了10000)

然后爬着爬着,就错误了???

我又爬了一次,让他输出错误的状态码,他给我返回了513

这是啥子嘛!

于是我加入了个判断状态码,状态码一错就休息一下重新再爬

if res.status_code == 513:
time.sleep(30) # 抓取错误时延时delay时间后重新抓取
return Hitokoto_spider()

然后。。。出门了一趟,回来你给我看这个???

原来我电脑休眠了

然后我把电脑的休眠调掉,把上面的 res.status_code == 513 改成 res.status_code != 200

开始爬!


在他爬的时候,我加入了json文件支持

由于本人不太喜欢手动创建,又怕我不小心勿删了文件导致程序出错

所以我给了个默认创建(所以这段代码比较长)

def read_config():  # 配置文件创建和读取
try:
if not os.path.exists("./data"):
os.mkdir("./data")
with open('./data/_config.json') as config_js:
config = js.load(config_js)
return config
except IOError:
with open('./data/_config.json', 'w', encoding='utf-8') as config:
configs = {
"path": "./data/Hitokoto.csv", # 文件输出路径
"times": 3000, # 抓取次数
"delay": 2, # 抓取休眠延迟,针对一言的QPS设置
"timeout": 60, # 连接超时时间(单位:秒)
# 读取显示
"from": True, # 来自什么作品
"from_who": True, # 来自谁
"creator": False, # 哪位用户提交的
"created_at": False # 何时提交
}
a = js.dumps(configs, indent=4, separators=(',', ':'))
config.write(a)
return read_config()

之后看到的一些调用就变成这样子了

cfg = read_config()
print(cfg["hitokoto"])

然后要把爬下来的一言存下来

我又加了一点点代码

def create_csv():
cfg=read_config()
with open(cfg["path"],"w+",newline="",encoding="utf8") as file:
csv_file = csv.writer(file)
head = ["id", "sort", "hitokoto", "from", "from_who", "creator", "created_at"] # 创建csv表头
csv_file.writerow(head)


def append_csv(inputs):
cfg = read_config()
with open(cfg["path"],"a+",newline='',encoding="utf8") as file:
csv_file = csv.writer(file)
data = [inputs]
csv_file.writerows(data)

同时对爬虫的代码进行一点点修改

def Hitokoto_spider():
cfg=read_config()
res=r.get("https://v1.hitokoto.cn",timeout=cfg["timeout"])
if res.status_code != 200:
time.sleep(cfg["delay"]) # 抓取错误时延时delay时间后重新抓取
return Hitokoto_spider()
data=res.json()
if not ids[data["id"]]:
print("{}:\t{}".format(data["id"],data["hitokoto"])) # 输出爬取内容
ids[data["id"]]=True

# 自动把分类码还原为分类
sorts = ["Animation", "Comics", "Games", "Literature", "Original", "Internet",
"Other", "Film and television", "Poetry", "Netease", "Philosophy", "Smart"]
x=ord(data["type"])-97
if 0<=x<12: sort = sorts[x]
else: sort = "Animation"

inputs = [data["id"], sort, data["hitokoto"], data["from"], data["from_who"], data["creator"], data["created_at"]]
append_csv(inputs)

之前的爬取错误也出来了

我原本以为是 TimeoutError

加入了 except TimeoutError 后仍然发生了这样的问题

不管了,不管他什么问题,通通 except !

def Hitokoto_spider():  # 爬取
try:
cfg=read_config()
res=r.get("https://v1.hitokoto.cn",timeout=cfg["timeout"])
if res.status_code != 200:
time.sleep(cfg["delay"]) # 抓取错误时延时delay时间后重新抓取
return Hitokoto_spider()
data=res.json()
if not ids[data["id"]]:
print("{}:\t{}".format(data["id"],data["hitokoto"])) # 输出爬取内容
ids[data["id"]]=True

# 自动把分类码还原为分类
sorts = ["Animation", "Comics", "Games", "Literature", "Original", "Internet",
"Other", "Film and television", "Poetry", "Netease", "Philosophy", "Smart"]
x=ord(data["type"])-97
if 0<=x<12: sort = sorts[x]
else: sort = "Animation"

inputs = [data["id"], sort, data["hitokoto"], data["from"], data["from_who"], data["creator"], data["created_at"]]
append_csv(inputs)
except:
time.sleep(60)
Hitokoto_spider()

加入重复爬取

上面的代码只能让我单次爬取,每次爬取都会覆盖原先的文档

所以我把ids数组存了下来

def save_ids():
ids_file = "./data/ids.npy"
np.save(ids_file, ids)


def load_ids():
ids_file = "./data/ids.npy"
ids=np.load(ids_file)
return ids

在每次爬取前load,在爬取结束时save就可以啦!

数据整理

因为API接口的随机性,爬到的id并不是按顺序爬到的,所以得进行排序

def sort_Hitokoto():
cfg = read_config()
Hitokoto_data = pd.read_csv(cfg["path"])
Hitokoto_data = Hitokoto_data.sort_values("id")
Hitokoto_data.to_csv(cfg["path"],index=False)

最终的结果长这样啦!


这个项目还没做完,之后可能会做API和GUI,以及非官方的一言收集

官方API的随机性使得我现在的爬取基本上是爬不到的

官方一言库共4396条

2020.12.21,30000次爬取,获取3323条

2020.12.22,30000次爬取,获取29条

2020.12.23,40000次爬取,获取0条

慢慢爬吧


附:直接利用官网的具体id爬取

这个方法是解析网站 https://hitokoto.cn/?id=1 的html来爬去

网站地址后面id接的数字对应的就是相应一言的id,范围:1-6623

优点:避免随机,一次爬取就可爬取全部

缺点:只能爬到id,一言文本和作者

直接贴代码,有兴趣可以自己复制去试试

注:id不连续

import requests
from bs4 import BeautifulSoup
import csv
import time

headers = {
"Cookie": "arccount62298=c; arccount62019=c",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
}

with open("hitokoto.csv","w+",newline="",encoding="utf8") as file:
csv_file = csv.writer(file)
head = ["id", "hitokoto", "from"] # 创建csv表头
csv_file.writerow(head)

for id in range(1,6624):
time.sleep(2)
try:
url = "https://hitokoto.cn/?id=" + str(id)
html = requests.get(url, headers = headers, timeout = 60)
soup = BeautifulSoup(html.text, "html.parser")

hitokoto = soup.find(id = "hitokoto_text")
# print(hitokoto.string)
author = soup.find(id = "hitokoto_author")
# print(author.string[3:])

with open("hitokoto.csv","a+",newline='',encoding="utf8") as file:
csv_file = csv.writer(file)
data = [[id,hitokoto.string,author.string[3:]]]
print("{}\t{}\t{}".format(id,hitokoto.string,author.string[3:]))
csv_file.writerows(data)
except:continue

(我就是用这个方式获取到了一言库的数据总数)