豆瓣 API 在没有 API key 的前提下已无法正常使用,本文已过时。若还有类似需求,需要基于豆瓣网页编写爬虫。另外,有插件可在 Typecho 博客上展示读书与电影清单:DoubanBoard。
handsome 主题带了一个叫豆瓣清单的自定义模板,可以把在豆瓣上标记的书籍和电影拉过来展示。我现在换了 Mirages 主题,有点想念这个功能,所以模仿着上线了两个页面。这里以读书展示页为例说说实现,电影页面除了扒数据的部分,其它完全类似。
示例页面:
后端
也就是如何把豆瓣上的数据扒回来。豆瓣 v2 接口中可以在不登录的情况下获取用户的书籍收藏信息,很方便;但是却没有电影收藏的接口,只能通过扒网页自己解析。获取电影收藏部分的代码我是从 handsome 主题抄的,所以就不贴了,只说一下书籍部分。获取书籍收藏的接口是:
https://api.douban.com/v2/book/user/[id]/collections?count=100
其中[id]
是豆瓣 ID,count
最多设置成 100,设置成更大时会被置为 100。从这个接口获取数据,并格式化保存到本地作为缓存。
updateBooks.php
<?php
$content=json_decode(file_get_contents('https://api.douban.com/v2/book/user/118077218/collections?count=100'));
$data=array();
foreach($content->collections as $value){
if($value->status!='read') continue; // 仅保存已读
$item=array("img"=>str_replace("/view/subject/m/public/","/lpic/",$value->book->image), // 防止图片链接 403
"title"=>$value->book->title,
"rating"=>$value->book->rating->average,
"author"=>$value->book->author[0],
"link"=>$value->book->alt,
"summary"=>$value->book->summary);
array_push($data,$item);
}
$file=fopen("books.json","w");
fwrite($file,json_encode($data));
fclose($file);
?>
更新缓存的话,我是在服务器上设了一个定时任务来访问 updateBooks.php。
getBooks.php
<?php
header("Content-type: application/json");
header("Access-Control-Allow-Origin: *"); // 防止跨域问题
$content=json_decode(file_get_contents('books.json'));
$total=count($content);
$from=$_GET['from'];
//返回从 from 开始的 10 条记录
if($from<0 || $from>$total-1) echo json_encode(array());
else{
$end=min($from+10,$total);
$data=array();
for ($index=$from; $index<$end; $index++) {
array_push($data,$content[$index]);
}
echo json_encode($data);
}
?>
前端
HTML
<div class="douban-list book-list"></div>
<center style="font-size: 1.1em"><span id="loadMoreBooks" style="cursor:pointer" onclick="loadBooks();"><i id="loadMoreBooks-i" class="fa fa-circle-o-notch"></i> 加载更多</span></center>
样式
.douban-list{
width:100%;
height:100%;
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
}
.douban-item {
position: relative;
margin-bottom: 15px!important;
width: 22%;
margin-right: 10px;
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
cursor: pointer;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
overflow: hidden;
}
.douban-item:hover{
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
@media screen and (max-width:768px){
.douban-item{
width: 30%;
}
}
@media screen and (max-width:400px){
.douban-item{
width: 45%;
}
}
.douban-thumb {
width: 100%;
padding-top: 141%;
background-repeat: no-repeat!important;
background-size: cover!important;
}
.douban-title {
margin: 8px !important;
font-size: 1.2em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap !important;
text-align: center !important;
color: #202020 !important;
font-weight: 700 !important;
}
.douban-info{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
overflow: auto;
transition: all 0.3s;
background-color: white;
color: black;
font-size: 0.9em;
line-height: 1.3em;
transform: translateY(100%);
}
.douban-info > p{
margin:1em!important;
margin-top:1em!important;
margin-bottom:0.3em!important;
}
.douban-info>.douban-info-basic{
white-space: nowrap!important;
overflow: hidden;
text-overflow: ellipsis;
}
.douban-info-show > .douban-info{
transform: translateY(0);
}
样式方面,由于书籍封面的长宽比相对比较固定,我自己发现 $\sqrt{2}$ 是一个不错的比例,因此问题主要是如何使 div
在缩放时保持长宽比。这里用到的原理是 padding-top
取值为百分比时,其最终的大小是根据 div
的宽度计算的,见上面 CSS 中 .douban-thumb
的写法。
JS(用到了 JQuery)
var curBooks=0;
function loadBooks(){
if(window.location.href!="https://blog.imalan.cn/book/") return;
$("#loadMoreBooks-i").addClass("fa-spin");
$.getJSON("https://api.imalan.cn/Douban/getBooks.php?from="+String(curBooks),function(result){
if(result.length<10){
$("#loadMoreBooks").html("没有啦");
}
$.each(result,function(i,ite){
var item=result[i];
var html=`<div title="点击显示详情" id="douban-book-item-`+String(curBooks)+`" class="douban-item">
<div class="douban-thumb" style="background:url(`+item.img+`)"></div>
<div class="douban-title">`+item.title+`</div>
<div class="douban-info" title="点击关闭详情">
<p class="douban-info-basic">
书名:`+item.title+`<br>
评分:`+item.rating+`<br>
作者:`+item.author+`<br>
链接:<a target="_blank" href="`+item.link+`">豆瓣阅读</a><br>
简介:<br>
</p>
<p class="douban-info-summary">
`+item.summary+`
</p>
</div>
</div>`;
$(".book-list").append(html);
curBooks++;
});
$("#loadMoreBooks-i").removeClass("fa-spin");
});
}
// 鼠标点击显示详情,点击其他区域关闭详情
$(document).click(function(e){
var target=e.target;
$(".douban-item").removeClass("douban-info-show");
$(".douban-item").each(function(){
if($(target).parent()[0]==$(this)[0] || $(target)==$(this)[0]){
$(this).addClass("douban-info-show");
}
})
})
$(document).ready(function(){
loadBooks();
});
一开始我始终不知道如何实现“点击其它区域关闭显示详情”这个功能,这个问题的本质是鉴别当前点击事件的对象具体是谁。后来在搜索引擎的帮助下发现 .click
事件有个 target
对象,那么只需要在点击事件中先把所有的详情显示都关闭了,然后遍历所有的 .douban-item
,若点击事件的 target
是当前遍历到的 item 或者是当前遍历到的 item 的子元素,那么就显示这个 item 的详情。由于一页中的 .douban-item
不是太多,所以性能不会差到哪里去。文字描述起来好绕……看上面的代码吧。
于是大功告成。
其它
有互联网上大量的资料支撑,想要自己做一点小玩具出来并不难。不过结合以前写追番清单那个插件的经历,以及最近微博、Twitter 等服务的接口权限相继收紧,我多少能闻到一些不妙的气息。豆瓣同理,以前是有获取用户电影收藏的接口的,现在却不能了(也许鉴权之后用高级接口可以)。豆瓣、微博、Twitter 这样的做法当然不难理解:虽然任何人都可以通过访问它们的网站来获取到用户的微博、tweet、收藏,但是重点是“去它们的网站”。这部分有价值的信息,这些公司慢慢都选择不再与整个互联网共享。
对此我感到非常遗憾。我非常尊敬豆瓣、Bangumi、IMDb 这些能在某个领域建立完整信息体系的网站,它们是整个互联网的财富;微博、Twitter 这类集中了大量 User Generated Content 的平台价值同样不可估量。作为一个普通的网民,虽然有些自私,我还是希望它们能保持开放的姿态。