Tag Archives: kindle

看书笔记 (2014.10)

决定记录一下自己看过的书,从现在开始。

最近几年有了Kindle之后,就几乎不看实体书了(除了某些专业书籍),即使买了实体书,也下载了D版的电子书来看,因为用Kindle看书太方便了:轻便,随时翻到最新的一页。

最近看完的一本是《费马大定理:一个困惑了世间智者358年的谜》,非常精彩,看得简直停不下来。
从古希腊的毕达哥拉斯定理讲起,一路经历各个数学家的各种故事,然后来到费马的这个初中学生都能理解的猜想,继续路各种精彩的故事,最终由怀尔斯(Andrew Wiles)完成证明。
故事里看到的各种数学证明或者猜想,有很多最初都是在宋公的离散数学以及《计算入门》的讲义里学过的,感觉甚是亲切。
书里面涉及到的一些定理的简单证明都在附录里,即使只是看这些小巧的附录,也是一件很有意思的事情。
总之,强烈推荐这本书,学数学的或者学计算机的都会感兴趣。

再之前看了(应该说补了)阿瑟·克拉克的《太空漫游》系列四部曲,似乎故事性不强,但是看过书的人一定会惊叹于他超越时代的想像——对于“未来”科技的构想和猜测,简直是神了!
这个系列也不用介绍了,看科幻的人一定知道的。
回想起来,作为科幻的爱好者,对于经典的科幻看得都相对比较晚,本来应该是高中、大学时看的书,都是在工作之后慢慢开始看的。不过,经典无论何时看都不过时。
顺便感谢一下《科幻世界》以及《科幻世界·增刊》 🙂

Share

用Calibre下载网页专题内容(非RSS Feed) 以及Referer的处理

神器Calibre有个很强大的Fetch News功能,可以下载RSS Feed然后生成电子书推送给Kindle。关于如何用recipe下载RSS Feed网上已经有不少相关的资料了。

但是如果想下载某一个专题,而这个专题又没有RSS输出怎么办呢?比如说:寻找太阳系的疆界

topic_outer_planets

答案是,recipe还有更逆天的手动parse index功能,正好用来分析一个单独的网页,提取出我们想要的文章。

这篇博客将会简要介绍这个功能。

另外,由于某些网站的图片会要求有Referer的HTTP header,如果没有下载时会403 Forbidden,所以下载图片里还要特别注意处理Referer。

Background:

Calibre内置了Fetch News功能,本质上是python实现的一个继承自 calibre.web.feeds.news.BasicNewsRecipe 的类,并且override了一些成员/函数的实现,以实现定制的功能。

不过一般来说所有的News都是通过抓取feed来实现的,比如说内置的“人民日报”的实现:

class AdvancedUserRecipe1277129332(BasicNewsRecipe):
  title          = u'人民日报'    # 标题
  oldest_article = 2    # 最近几天之前的文章
  max_articles_per_feed = 100    # 最大的文章数
  ...
  feeds          = [
    (u'时政', u'http://www.people.com.cn/rss/politics.xml'),
    (u'国际', u'http://www.people.com.cn/rss/world.xml'),
    (u'经济', u'http://www.people.com.cn/rss/finance.xml'),
    (u'体育', u'http://www.people.com.cn/rss/sports.xml'),
    ...
  ]    # 定义具体的feed名称和url
  keep_only_tags = [...]    # 抓取到的网页里保留的内容
  remove_tags = [...]    # 抓取到的网页里去除的内容
  remove_tags_after = [...]    # 抓取到的网页里去除这个tag之后的内容
  ...

一般来说抄一下Calibre内置的recipe,定义以上的变量就可以搞定绝大部分RSS Feed了。

那么如何搞定像“寻找太阳系的疆界”这样的没有RSS输出的专题文章呢?

Solution:

查看 Calibre的API Document,注意到parse_index()这个函数,它说:

This method should be implemented in recipes that parse a website instead of feeds to generate a list of articles.<br />
...
It must return a list. Each element of the list must be a 2-element tuple of the form ('feed title', list of articles).<br />
Each list of articles must contain dictionaries of the form:
{
 'title'       : article title,
 'url'         : URL of print version,
 'date'        : The publication date of the article as a string,
 'description' : A summary of the article
 'content'     : The full article (can be an empty string). Obsolete
 do not use, instead save the content to a temporary
 file and pass a file:///path/to/temp/file.html as
 the URL.
}

并且提到:

For an example, see the recipe for downloading The Atlantic.

也就是说,可以参考内置的 The Atlantic的recipe,它是parse一个website而不是一个feed。打开这个recipe:

class TheAtlantic(BasicNewsRecipe):
  title      = 'The Atlantic'
  __author__ = 'Kovid Goyal and Sujata Raman'
  description = 'Current affairs and politics focussed on the US'
  INDEX = 'http://www.theatlantic.com/magazine/toc/0/'
  language = 'en'
  remove_tags_before = dict(name='div', id='articleHead')
  remove_tags_after  = dict(id='copyright')
  remove_tags        = [dict(id=['header', 'printAds', 'pageControls'])]
  ...
def parse_index(self):
  ... # Parse the webpage and return feeds

很清楚,主要实现了parse_index这个函数。

那么我们就可以参考这个实现,来写自己parse changhai.org的代码。

Step1 实现parse_index

直接看code吧,注释很容易明白:

def parse_index(self):
  articles = []  # 文章列表
  feeds = []  # 最终要返回的
  soup = self.index_to_soup(self.INDEX)  # 获取网页完整的内容
  feed_title = self.tag_to_string(soup.find('title'))  # 网页的标题
  self.log.debug("Feed Title: " + feed_title)
  for table in soup.findAll('table', attrs={'class':'inside-article-noborder'}):
    # Main articles
    for post in table.findAll('td'):  # 文章列表都在 inside-article-noborder 的 table 里,每一个td是一项
      a = post.find('a', href=True)  # 取出链接
      title = self.tag_to_string(post)  # 文章标题
      url = a['href']
      if "#" not in url:  # 如果url里包含"#",其实是指向了同一个网页的某个anchor,忽略这样的项
        self.log.debug("Article title and url: ")
        self.log.debug(title + ": " + url)
        url = self.INDEX + url  # 生成完整的url
        articles.append((
          {'title':title,
           'url':url,
           'description':'',
           'date':''}))  # 添加到文章列表里
    for post in soup.findAll('p', attrs={'class':'center'}): # 术语简介 | 参考文献 在 class: center的p里面
      for a in post.findAll('a', href=True):  # 同理取出文章的标题和url
        title = self.tag_to_string(a)
        url = a['href']
        self.log.debug("Article title and url: ")
        self.log.debug(title + ": " + url)
        url = self.INDEX + a['href']
        articles.append((
          {'title':title,
           'url':url,
           'description':'',
           'date':''}))
  if articles:
    feeds.append((feed_title, articles))  # 最后生成feed,这里只有一个feed,包含多篇文章
  return feeds

这样每篇文章都能被download下来了,下面要处理每篇文章的内容。

Step 2 处理正文内容

分析正文的网页内容,会发现:

正文内容都在 main-body 的table里,所以

keep_only_tags = [
  dict(name='table', attrs={'id':['main-body']}),
]

“返回目录 | 下一篇”这样的link可以忽略,都在 class left 和right的p里面

remove_tags = [
  dict(name='p', attrs={'class':['left', 'right']}),
]

“站长近期发表的作品”及以后的内容应该忽略,它位于article-data-claim的后面,所以

remove_tags_after = [
  dict(name='p', attrs = {'class' : ['article-date-claim']}),
]

好了,到这一步,文章的内容也能完美提取出来了。

但是,正文里的图片还是没有显示,原因在calibre的log里:

Could not fetch image  http://www.changhai.org/articles/science/astronomy/outer_planets/images/kepler_platonic.png
reply: 'HTTP/1.1 403 Forbidden\r\n'

webserver直接返回403错误了,这一般是因为图片下载时没有Referer的HTTP header,算是一算防盗链的功能吧。

所以我们还需要处理Referer。

Step3 在HTTP Request里添加Referer

通过Google,发现可以通过自定义browser.open_novisit这个函数来实现添加HTTP header

def get_browser(self):
  br = BasicNewsRecipe.get_browser(self)
  orig_open_novisit = br.open_novisit
  def my_open_no_visit(url, **kwargs):
    req = mechanize.Request( url, headers = { 'Referer': self.INDEX, }) # 添加Referer的header
    return orig_open_novisit(req)
  br.open_novisit = my_open_no_visit # 把open_novisit这个函数重新定义成自己的函数
  return br

理论上这样就OK了,然后实际运行的时候会发现,下载文章和图片的时候,根本就没有调用到my_open_no_visit(),图片仍然下载失败。

这下就只能看Calibre的源代码了——通过代码和打log,注意到_fetch_article()里面会调用clone_browser(),而这个clone完的browser的open_no_visit函数还是原生的,并不是自定义的函数。假如这个不是bug,就说明俺的recipe是有点exotic了:

Clone the browser br. Cloned browsers are used for multi-threaded downloads, since mechanize is not thread safe. The default cloning routines should capture most browser customization, but if you do something exotic in your recipe, you should override this method in your recipe and clone manually.

好吧,那就自己实现一个clone_browser()吧,同时也更新自己的get_browser():

_cachedBrowser = None  # 自己的browser
def get_browser(self):
  if self._cachedBrowser is None:
    br = BasicNewsRecipe.get_browser(self)
    orig_open_novisit = br.open_novisit
    def my_open_no_visit(url, **kwargs):
      req = mechanize.Request( url, headers = { 'Referer': self.INDEX, })
      return orig_open_novisit(req)
    br.open_novisit = my_open_no_visit
    self._cachedBrowser = br  # 保存browser
  return self._cachedBrowser
def clone_browser(self, br):
  if self._cachedBrowser is None:
    raise Exception("No browser")
  br = BasicNewsRecipe.clone_browser(self, br)
  br.open_novisit = self._cachedBrowser.open_novisit  # 设置clone出来的browser的open_novisit函数
  return br

OK, 这下Referer的问题也解决了,生成的文章里也完美包含了图片。

kindle_outer_planets kindle_outer_planets_2

Finally:

完整的code放在github(见calibre_recipes)上了,当然code还有待完善,比如说可以合并common的code,以后再慢慢改了。

目前实现了3个专题的recipe:

Q.E.D.

p.s. 顺便推荐一下卢昌海老师的书吧,上面的这些文章都已经出版了,并且有的也有Kindle的版本,推荐购买 🙂
p.p.s 在微博上发现@敲代码的张洋已经有了一些抓取网页的Recipe,包括著名的MIT的SICP,以及O’Reilly的开放书籍,很不错。代码也在Github

Share

Kindle Paperwhite 添加中文字体

入手了国行的Kindle Paperwhite,相比Kindle 3有了更好的中文字体支持,原生的“宋体“效果已经相当不错了。
不过以前按照《Kindle 中文阅读终极优化指南》看习惯了“方正准雅宋”,KPW自带的宋体显得有点淡,于是我又开始折腾字体了。。。

The Precondition
首先,KPW添加字体是很简单的一件事情,也不需要越狱:

  1. 用USB线连接KPW到电脑;
  2. 在Kindle的根目录上创建一个空文件 USE_ALT_FONTS (注意不要有扩展名);
  3. 在Kindle的根目录上创建目录 fonts;
  4. 把想要的字体拷到那个fonts目录,如果字体是一个系列的,粗体/斜体按如下命名:
    • Fontname-Regular.ttf     # 普通
    • Fontname-Italic.ttf          # 斜体
    • Fontname-Bold.ttf          # 粗体
    • Fontname-BoldItalic.ttf   # 又粗又斜
  5. 拔掉USB线,重启KPW (菜单->设置->菜单->重启);
  6. 在字体菜单里选择上面添加的字体.

The Problem
但是如果用《优化指南》里提到的这四个字体:

会发现,KPW会把它们认成4个不同的字体,选择“方正准雅宋”之后,也没办法自动使用对应粗体。其实这也很正常,上面的四个字体根本就不是一个Font Family的,KPW自然不认了。

The Solution
解决办法是,用字体编辑器把它们变成同一个Font Family!
在Ubuntu下我试过FontForge, TTFEdit, Ttx这些工具。
FontForge功能太强而且似乎生成的font文件会有问题;
TTFEdit对utf8或者gbk编码的字符串支持有问题;
Ttx其实是一个把TTF转换成xml文件,然后把xml文件转回成ttf的工具。

最后我用ttx搞定了。
1.

ttx -o CJK_Regular.ttx CJK_Regular.ttf  # 把ttf转换成ttx文件(本质就是个xml)

2.

编辑CJK_Regular.ttx
在<name>里,nameID=1是FontFamily,把它改成FZYaSong-GBK
在<OS_2>里,usWeightClass是指字体的Weight,把它改成500 (Medium),顺便把bWeight改成“6”(也许这个改动没有必要)

2.x.

同样的,把另外三个ttx文件里的FontFamily都改成FZYaSong-GBK
CJK_Bold.ttx,CJK_BoldItalic的usWeightClass改成700,bWeight改成8;
CJK_Italic的usWeightClass改成400,bWeight改成6;

3.

ttx -o FZYaSongMod-Regular.ttf CJK_Regular.ttx # 重新生成ttf字体
 另外三个文件同理

这样就生成了FZYaSongMod的四个ttf文件,FontFamily都是FZYaSong-GBK, Weight都是对应的值。然后把这四个文件拷到KPW的fonts文件夹里,重启,搞定!
KPW的显示效果终于和Kindle3一样了,内牛满面啊…

The Last
这四个文件已经打包好,有需求的可以直接用。下载在此: FZYaSongMod.tar.bz2
声明: 更改字体信息仅限学习用,不得作为商业用途。

p.s. 感谢@wzyboy同学《优化指南》 本文修改的字体来源于他的博客。
p.p.s. 推荐越狱之后安装kindlepdfviewer,完美集成于KPW,看PDF效果更好~

Share

买平板小记…

    前段时间一直长草平板,正好Liu桑在米国,元旦前回来,可以帮忙带,于是可以出手了——

    本想买个Nexus 10,虽然太丑,但鉴于其强大的硬件配置和相对不错的价格,一直想买它。Google Play Store上的硬件不支持中国地区的购买,不过用个自己的翻墙代理,轻车熟路地点开购买的link,用个米国的地址当信用卡的帐单地址,可以顺利地用Google Checkout支付。然而看最终价格,由于Liu桑所在的不是免税的州,东西$399+运费近13刀+税22多刀(粗略记得的数目),一下子贵了200多rmb。觉着亏,不该给美帝交税的,况且Google你丫收13刀的运费,太黑了!不买!

    转而考虑Amazaon的Kindle Fire HD 8.9,更小巧的设备,但又不至于像Nexus7那样太小,感觉可能正合适,299刀的价格,也基本上对得起那个硬件。软件方面,没有Google的服务是个问题,但估计可以自己刷CM,或者也许可以另外装相应的服务,总之可以折腾着搞定,就它了。

    某天上午,点开KindleFireHD8.9的link,填好Liu桑的地址,下单付款,一切顺利,花了$299等收货。
    那天下午,“什么值得买”上看到说Kindle平板系列降价$50了了,我一看,丫还真是,上午的单子下完没几个小时(估计过了中午)amazon上就搞特价给了$50的优惠码,当时感觉那个亏啊。。。

    尝试取消之前的订单,但是系统说因为已经出货了,不能cancel,只能return。好吧,那就填个退货申请,理由写上因为价格变化而“描述不符”,可以原价退货,amazon还会安排UPS上门取货,挺方便的样子;(注意退货原因,要是选了错误的订单,是要被amazon扣掉几刀并且不免UPS上门取货的费用的,选描述不符就啥费用都没了)
    然后重新下单,以$249的价格买了同样的东东,等收货~

    几天后,Liu桑郁闷地说,UPS的小哥们天天上门来取货,但是货还没到,取啥呀。。。
    同时,amazon邮件说,order已经return成功了,refund会在2~3个工作日内退回到俺的信用卡——擦,东西还没退,钱先退了,受宠若惊啊!看来米国人做生意都靠诚信。要是在国内这么搞,估摸着n多东西会找不回来吧?

    再一天之后,发现钱已经顺利地退回到信用卡,Liu桑也已经收到两个货,并且退了一个,一切都很正常。
    嗯,感谢amazon,$249刀的8.9寸平板,算是值了。等Liu桑回国了~

    货还没到,上张图解解馋
kindle-fire-hd-89

Share

2012 – 国庆琐事

· 今年中秋、国庆第一次高速公路免费,知道会很堵,但还是想凑这个热闹——于是从上海到无锡,开了6个多小时。在阳澄湖休息区附近,整个沪宁高速成了一个停车场,甚至有人停车爬下草坪跑到休息区去吃东西。。。倒也是难得的体验。
    后来发现了Google Maps的路况似乎还蛮准的,一看苏州到无锡整个都是拥堵的红色,我算是比较明智地从苏州新区下了高速,走312国道回去了。后来听说有邻居同样是中午出发,走高速晚上9点多才到的无锡,汗。。。

· 小华同学从英国回来,几个初中同学找了这个机会聚一下。在无锡的同学,都住城里了,变化都不大。护士、光伏、装修,各行各业都有。
  几个在堰桥的高中同学也喊聚会,也一起参加了。相比起来,高中同学从事的行业要稍微集中一点了,主要是IT、金融行业了。
  聊到初、高中的老师的时候,我发现自己有印象的老师屈指可数了。。。 初中的班主任,物理,数学老师还记得;高中的班主任*2,馒头,10班的化学老师,也就这些还记得了,sigh。。。

· 在家发现了一个太阳能手电筒——没错,就是“国产零零漆”里传说中的太阳能手电!不过这个还是有蓄电池,还带手摇发电的。
  我一直想找块太阳能电池板给我的Kindle充电的(理想状态下,kindle就永远不需要插电了),果断把手电筒的电池板拆了。量了一下,太阳底下有很稳定的5.x伏电压,感觉有戏。
  回到上海,剪掉根USB线,把电池板的正、负极接到USB的V+,GND线上,试了一下,是有电,不过手机认为似乎是接着电脑——Google一下后发现,充电器(至少moto的)的D+/D-是短接的,这很简单,把USB线的两根线接一下就OK了,然后试试手机上的充电——看上去一切正常。
  ——然而,80%电量的手机,充了一段时间电之后,只剩60%多了,囧。
  然后把线拆开来,接上万用表测电流,很囧的发现在3mA左右,等于没有了;另外Google后发现一般都会接个二极管,以保证不会有逆流(相当于电池给太阳能板供电)。
  好吧,结论:这块板子太小,以后得找块大点的太阳能板才行,另外还得接个二极管才能真正实用。

Share