场景:前几天我一个同事手误删了一整个文件夹的书签,还是没登录账号的情况下,也没点左下角的撤销按钮,不知道怎么恢复
网上有一种方法,是进到C:\\User\\AppData\\Local\\Google\\Chrome\\User Data\\Default目录下,将Bookmarks文件重命名为任意名称,将Bookmarks.bak文件去掉.bak后缀,然后重启浏览器就能恢复到.bak文件最后修改时间的书签内容
在同事电脑试了,没啥用,又因为同事当时需用电脑,暂时不能试重启是否有效
当时是直接在同事电脑上操作,我试过想看看Bookmarks文件里面是什么内容,但是双击后无法打开,系统是win10,后来我用自己电脑去试,双击能用文本编辑器打开,系统是win11
Bookmarks/Bookmarks.bak文件的内容结构是这样的:
{
"checksum": "xxx",
"roots": {
"bookmark_bar": {
"children": [
{
"date_added": "xxx",
"date_last_used": "xxx",
"guid": "xxx",
"id": "xxx",
"name": "百度一下,你就知道",
"type": "url",
"url": "https://www.baidu.com/"
}
],
"date_added": "xxx",
"date_last_used": "0",
"date_modified": "xxx",
"guid": "xxx",
"id": "xxx",
"name": "书签栏",
"type": "folder"
},
"other": {
"children": [
{
"date_added": "xxx",
"date_last_used": "xxx",
"guid": "xxx",
"id": "xxx",
"name": "pixiv",
"type": "url",
"url": "https://www.pixiv.net/"
}
],
"date_added": "xxx",
"date_last_used": "0",
"date_modified": "xxx",
"guid": "xxx",
"id": "xxx",
"name": "其他书签",
"type": "folder"
},
"synced": {
"children": [],
"date_added": "xxx",
"date_last_used": "0",
"date_modified": "0",
"guid": "xxx",
"id": "xxx",
"name": "移动设备书签",
"type": "folder"
}
},
"version": 1
}
结构很简单,属性也都是重复的,那就好办了~
直接改文件名的方式暂时看不到效果,那就只能根据这个json手动再录一遍呗~
咋可能手动录,咱是程序猿,咋可能去干这种费时费力的体力活!
写程序的终极目标是为了偷懒!
不想一个个录,那就批量导呗,咋导?
先导出书签,再导入书签,不就行了,无非是内容不一样
那怎么批量改内容呢?
我的想法不是改内容,而是根据他的结构将Bookmarks文件的内容填充进去,生成一个新的导入书签文件
那导出书签的文件结构是怎样的呢
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
It will be read and overwritten.
DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL><p>
<DT><H3 ADD_DATE="xxx" LAST_MODIFIED="xxx" PERSONAL_TOOLBAR_FOLDER="true">书签栏</H3>
<DL><p>
<DT><A HREF="https://www.baidu.com/" ADD_DATE="xxx" ICON="data:image/png;base64,xxx">百度一下,你就知道</A>
</DL><p>
<DT><A HREF="https://www.pixiv.net/" ADD_DATE="xxx" ICON="data:image/png;base64,xxx">pixiv</A>
</DL><p>
诶,也很简单,最外层dl-p,文件夹用dt-h3然后再包一层dl-p放网址,网址则是dt-a
有迹可循,那就好办
以下是用Java编写实现读取Bookmarks.bak文件内容然后按照导入书签文件内容格式填充功能的代码
主类Main
import com.alibaba.fastjson.JSON;
// import helper.FileHelper;
// import model.BookmarksModel;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author Canser
* Bookmarks.bak是书签数据(Bookmarks)的备份文件,备份存在一定时差,故可作为短时间内误操作的书签还原
* 短时间内指,误操作时间处于上一次备份和下一次备份的时间之内,如果.bak的修改时间已经超过误操作的时间,则于事无补
* 注:出现误操作之后,千万不要再对书签进行操作,否则.bak会更新
*/
public class Main {
public static void main(String[] args) {
// 用户根目录
String userDir = System.getProperty("user.home");
// 这个路径是不会变动的
String bookmarksPath = userDir + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks.bak";
File bookmarks = new File(bookmarksPath);
// 获取文件内容
String content = FileHelper.readFile(bookmarks);
// 字符串转实体类,这里用的是fastjson
BookmarksModel model = JSON.parseObject(JSON.parse(content).toString(), BookmarksModel.class);
// 获取导入数据的html内容
String html = getHtmlFromModel(model);
// 创建和写入html中
FileHelper.outputToFile(html);
}
private static String getHtmlFromModel(BookmarksModel model) {
// 存放所有代码行
List<String> list = new ArrayList<>();
// 外层结构
list.add("<!DOCTYPE NETSCAPE-Bookmark-file-1>");
list.add("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">");
list.add("<TITLE>Bookmarks</TITLE>");
list.add("<H1>Bookmarks</H1>");
// 书签栏数据
getChildren(0, list, model.roots.bookmark_bar);
// 其他书签数据
getChildren(0, list, model.roots.other);
// 拼接换行后返回
return StringUtils.join(list, "\n");
}
private static void getChildren(int blankNum, List<String> list, BookmarksModel.Children... children) {
// 前置空格,这个对象纯粹是为了html代码看的美观,实际没啥用
String blankStr = " ".repeat(blankNum);
// 最外层父级dl
list.add(blankStr + "<DL><p>");
// 前置空格数+4,即一个tab
blankNum += 4;
for (BookmarksModel.Children child : children) {
if ("folder".equals(child.type)) {
// 如果是文件夹
list.add(String.format("%s<DT><H3 ADD_DATE=\"%s\" LAST_MODIFIED=\"%s\">%s</H3>",
" ".repeat(blankNum), child.date_added, child.date_last_used, child.name));
if (child.children != null && child.children.length > 0) {
// 存在子文件夹则递归
getChildren(blankNum, list, child.children);
}
} else if ("url".equals(child.type)) {
// 如果是地址
list.add(String.format("%s<DT><A HREF=\"%s\" ADD_DATE=\"%s\">%s</A>",
" ".repeat(blankNum), child.url, child.date_added, child.name));
}
// 其实属性ADD_DATE和LAST_MODIFIED可以不放,毕竟导入后是属于新增的,这些参数都会被覆盖
}
list.add(blankStr + "</DL><p>");
}
}
字符串映射实体类BookmarksModel
import lombok.Data;
/**
* @author Canser
* 实际用到的字段只有:
* Roots.bookmark_bar、Roots.other、Children.date_added、Children.date_last_used、Children.name、Children.type、Children.url
* 其他的字段和类其实是没用到的,可以看情况删除
*/
@Data
public class BookmarksModel {
public String checksum;
public Roots roots;
public String version;
@Data
public static class Roots {
public Children bookmark_bar;
public Children other;
public Children synced;
}
@Data
public static class Children {
public String date_added;
public String date_last_used;
public String guid;
public String id;
public String name;
public String type;
public String url;
public MetaInfo meta_info;
public Children[] children;
@Data
public static class MetaInfo {
public String power_bookmark_meta;
}
}
}
文件帮助类FileHelper
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
/**
* @author Canser
*/
public class FileHelper {
public static String readFile(File txt) {
ArrayList<String> set = new ArrayList<>();
if (!txt.exists()) {
System.out.println("文件 " + txt.getName() + " 不存在!");
return "";
}
try (BufferedReader reader = new BufferedReader(new FileReader(txt))) {
String str = reader.readLine();
while (str != null) {
set.add(str);
str = reader.readLine();
}
} catch (Exception e) {
e.printStackTrace();
}
return String.join("\n", set.toArray(new String[0]));
}
public static void outputToFile(String content) {
try {
// 输出的文件路径自定义
File outHtml = new File("src/main/resources/out.html");
if (!outHtml.exists() && !outHtml.createNewFile()) {
return;
}
try (FileWriter fileWriter = new FileWriter(outHtml)) {
fileWriter.write(content);
fileWriter.flush();
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
必要的Maven依赖项
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
其中commons-lang3这个包,这里面只用到了StringUtils.join这一个方法,我为了图省事一句话解决就把这个包引进来了,一开始我是用StringBuilder的,但是这样的话每一行字符串后面都得手动加个\n,不太雅观,就换join拼接了,各位可以根据自己情况调整
FileHelper这个帮助类里我没怎么写判断,各位也可根据实际情况自行添加
运行main方法后找到生成的html文件,然后在chrome导入书签,再稍微调整一下位置就可以了(chrome导入会归到一个“已导入”的文件夹)
(这个代码生成的结构和导出书签的结构有略微区别,区别在于“其他书签”一栏,我没放到最外层的dl-p里,而是另起了一个dl-p,不过不影响,导入结束后拖一下位置就行了)
Bookmarks.bak是书签数据(Bookmarks)的备份文件,备份存在一定时差,故可作为短时间内误操作的书签还原
短时间内指,误操作时间处于上一次备份和下一次备份的时间之内,如果.bak的修改时间已经超过误操作的时间,则于事无补
注:出现误操作之后,千万不要再对书签进行操作,否则.bak会更新
完