用Python扫描图书条形码并构建个人藏书清单

自己攒了几箱子的书,想着给它们建个数据库,试了知乎上提到的几款软件,要么是软件已经凉了,要么是需要收费。最可恨的是,我用某小程序顺利扫描了七十余本书,最后到处的表格里却出现了一句“说明:未付费书馆仅支持导出10条数据,付费书馆导出数据无限制。”当然了,收费无可厚非,你能不能提前说一声啊!于是我一气之下决定自己写一个Python程序,扫描图书的条形码(ISBN),通过公开的API获取相关信息,并保存图书信息到本地Excel表格中,同时也获取图书封面到本地,构建自己的个人藏书清单。

先看看最终效果。

清单:

image-20220123074515645

封面:

image-20220123074708441

思路

  1. 用python OpenCV调用摄像头;
  2. 使用zbar库扫描ISBN条形码;
  3. 调用查询接口获取图书信息;
  4. 将图书信息保存;

调用摄像头

  1. 安装python的OpenCV库opencv-python

    1
    pip3 install opencv-python

    如果没有安装numpy则会自动安装上去;

  2. 主要代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import cv2

    cap = cv2.VideoCapture(0)

    if not cap.isOpened():
    print("Cannot open camera")
    exit(0)

    while True:
    ret, frame = cap.read()

    if not ret:
    print("Failed to capture image")
    continue

    cv2.imshow('frame', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
    break

    cap.release()
    cv2.destroyAllWindows()

扫描条形码

  1. 安装python的zbarpyzbar

    1
    pip3 install pyzbar
  2. 解决错误Unable to find zbar shared library(Ubuntu)

    1
    sudo apt-get install libzbar-dev
  3. 扫描条形码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import cv2
    from pyzbar.pyzbar import decode

    cap = cv2.VideoCapture(0)

    if not cap.isOpened():
    print("Cannot open camera")
    exit(0)

    while True:
    ret, frame = cap.read()

    if not ret:
    print("Failed to capture image")
    continue

    cv2.imshow('frame', frame)
    barcodes = decode(frame)

    for barcode in barcodes:
    print("barcode = ", barcode.data)

    if cv2.waitKey(1) & 0xFF == ord('q'):
    break

    cap.release()
    cv2.destroyAllWindows()

检索ISBN数据

  1. 首先找到一个公开的ISBN接口,这里我用的是中文ISBN公开信息查询接口 | 极客分享 (jike.xyz),用法如下(需要申请apikey,鼓励大家给API提供者捐赠项目维持经费):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import requests

    url = "https://api.jike.xyz/situ/book/isbn/"

    isbn = "9787020024759"

    apikey = "替换为申请的apikey"

    full_url = url + isbn + "?apikey=" + apikey

    response = requests.request("GET", full_url)

    print(response.text)
  2. 安装依赖

    1
    pip3 install requests
  3. 整合思路

    1. 扫描到一个ISBN后暂停捕获画面;
    2. 通过API获取该ISBN对应的书籍数据;
    3. 处理该数据(保存或不保存);
    4. 继续扫描;
  4. 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    from time import sleep
    import cv2
    from pyzbar.pyzbar import decode
    import requests

    url = "https://api.jike.xyz/situ/book/isbn/"

    isbn = "9787020024759"

    apikey = "替换为申请的apikey"

    cap = cv2.VideoCapture(0)

    if not cap.isOpened():
    print("Cannot open camera")
    exit(0)

    while True:
    ret, frame = cap.read()

    if not ret:
    print("Failed to capture image")
    continue

    cv2.imshow('frame', frame)
    barcodes = decode(frame)

    for barcode in barcodes:
    isbn = barcode.data.decode("utf-8")
    full_url = url + isbn + "?apikey=" + apikey
    response = requests.request("GET", full_url)
    print(response.text)
    sleep(3)

    if cv2.waitKey(1) & 0xFF == ord('q'):
    break

    cap.release()
    cv2.destroyAllWindows()

图书信息数据的解析与保存

  1. 返回的数据为json格式,需要解析出具体的字段,提供API的网站给出了返回参数的说明,其实远远不止这些,可以print出来看一下:

    名称 类型 说明
    subname string 书名
    author string 作者
    authorIntro string 作者简介
    photoUrl string 图片封面
    publishing string 出版社
    published string 出版时间
    description string 图书简介
    doubanScore string 豆瓣评分

    这里使用json.loads()函数来解析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    json_data = json.loads(response.text)
    ret = json_data["ret"]
    msg = json_data["msg"]
    data = json_data["data"]
    print("返回信息: " + msg + "(ISBN" + isbn + ")")
    if ret == 0 and data != None:
    is_save = input("是否保存?(y/n)")
    if is_save == 'y' or is_save == 'Y':
    save_book(data)
    else:
    print("未找到该书籍")
    sleep(1)
  2. 可以将数据保存到excel,可以使用pandas库来实现,操作比较方便,首先安装依赖:

    1
    pip3 install openpyxl, pandas
  3. 具体保存的思路就是,如果没有文件先创建文件,设置列首,如果已经有了那就追加数据,方法是先把数据读出来,然后再追加,然后写回去:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def save_book(data):
    file_path = os.path.join(os.path.dirname(__file__), 'books.xlsx')
    if not os.path.exists(file_path):
    df = pd.DataFrame(columns=['id', 'name', 'author', 'subname', 'translator', 'publishing', 'designed',
    'code', 'douban', 'doubanScore', 'brand', 'weight', 'size', 'pages', 'photoUrl', 'localPhotoUrl',
    'price', 'froms', 'num', 'createTime', 'uptime', 'authorIntro', 'description', 'reviews', 'tags'], dtype=str)
    df.to_excel(file_path, index=False)
    df = pd.read_excel(file_path, dtype=str)
    for key in data.keys():
    data[key] = str(data[key])
    df = df.append(data, ignore_index=True)
    df.to_excel(file_path, index=False)
  4. 还有就是发现有一个数据项为photoUrl,这是书籍封面的URL,同样可以抓取了保存下来,实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    photoUrl = data['photoUrl']
    if photoUrl != "":
    print("save photo from" + photoUrl)
    try:
    r = requests.request("GET", photoUrl)
    save_path = "./photo/" +str(data["code"]) + "." + photoUrl.split('.')[-1]
    print(save_path)
    with open(save_path, 'wb') as f:
    f.write(r.content)
    except:
    print("save photo failed")

最终实现

整理以上代码,最终完整实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import json
import os
from time import sleep
import cv2
from pyzbar.pyzbar import decode
import requests
import pandas as pd

# 将图书数据和封面图片保存到本地
def save_book(data):
# 书籍列表存放的文件
file_path = os.path.join(os.path.dirname(__file__), 'books.xlsx')
print(file_path)
# 如果文件不存在,则创建文件并添加表头
if not os.path.exists(file_path):
# 表头
df = pd.DataFrame(columns=['id', 'name', 'author', 'subname', 'translator', 'publishing', 'designed',
'code', 'douban', 'doubanScore', 'brand', 'weight', 'size', 'pages', 'photoUrl', 'localPhotoUrl',
'price', 'froms', 'num', 'createTime', 'uptime', 'authorIntro', 'description', 'reviews', 'tags'], dtype=str)
# 保存到本地
df.to_excel(file_path, index=False)
# 如果文件存在,则读取文件并追加数据
# 读取数据到dataframe中,数据类型为str防止格式错误
df = pd.read_excel(file_path, dtype=str)
# 把数据都转换为字符串
for key in data.keys():
data[key] = str(data[key]) if data[key] is not None else ''
# 追加数据到dataframe中
df = df.append(data, ignore_index=True)
# 保存到本地
df.to_excel(file_path, index=False)

# 将图书封面保存到本地
# 图书封面的url
photoUrl = data['photoUrl']
if photoUrl != "":
print("从此URL抓取封面: " + photoUrl)
try:
# 请求图书封面
r = requests.request("GET", photoUrl)
# 图书封面的本地路径
save_path = os.path.join(os.path.dirname(__file__), "photo")
print(save_path)
if not os.path.exists(save_path):
os.makedirs(save_path)
save_path = os.path.join(save_path, str(data["code"]) + "." + photoUrl.split('.')[-1])
# 保存图书封面到本地
with open(save_path, 'wb') as f:
f.write(r.content)
print("保存封面成功")
except:
print("保存封面失败")

if __name__ == '__main__':
# 抓取数据用的API和apikey
url = "https://api.jike.xyz/situ/book/isbn/"
apikey = "替换为申请的apikey"

# 获取并打开摄像头
cap = cv2.VideoCapture(0)

# 测试摄像头是否打开
if not cap.isOpened():
print("无法打开摄像头")
exit(0)

# 对摄像头获取到的每一帧进行处理
while True:
# 获取一帧
ret, frame = cap.read()

# 判断是否成功抓取到了图像
if not ret:
print("抓取图片失败")
continue

# 显示捕获到的帧
cv2.imshow('摄像头', frame)

# 按下q键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break

# 尝试解析帧中的条形码
barcodes = decode(frame)

# 如果没有解析到条形码,则继续下一次循环
if len(barcodes) == 0:
continue

# 如果解析到了条形码,则解析条形码中的ISBN数据并抓取图书数据
for barcode in barcodes:
# 获取ISBN数据
isbn = barcode.data.decode("utf-8")
# 拼接完整的API请求URL
full_url = url + isbn + "?apikey=" + apikey
# 发送API请求
response = requests.request("GET", full_url)
# 解析响应内容
json_data = json.loads(response.text)

# 解析响应内容判断是否成功
ret = json_data["ret"]
msg = json_data["msg"]
data = json_data["data"]
# 提示相应信息
print("返回信息: " + msg + "( ISBN=" + isbn + " )")
# 如果成功获取到了图书数据,则可以选择是否保存图书数据
if ret == 0 and data != None:
is_save = input("是否保存?(Y/n)")
if is_save == 'y' or is_save == 'Y' or is_save == '':
save_book(data)
# 如果获取图书数据失败,则提示并继续下一次循环
else:
print("未找到该书籍")
sleep(1)

# 释放摄像头
cap.release()
# 关闭所有窗口
cv2.destroyAllWindows()