书友群里经常有人闹书荒,求推荐,于是我想了想,把豆瓣里面的小说们都爬了下来。
还是用的老方法,urllib+正则提取,没有用到scrapy这么高科技的东西(其实是因为windows下太难装了)。不过这一次我用的是python3。其实我只是在网上找了一下http头应该怎么写,那个网页用的是python3的,我就也用了。python2应该类似。其实方法还是相当简单的。
首先看看,豆瓣上面标签为“小说”的书的地址:
https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4
试着前后翻翻页,能看到这个url后面加上了
?start=0&type=T
这两个get的参数,其中start表示从第几本书开始(20本书一页),type表示书的排序方式。其实豆瓣有默认的按照评分排序,但是并不准,所以,就按默认排序吧,下载下来自己手动sort一下就好了!
首先抓下来一个网页:
def get_html(url): """ 抓取网页 """ cj = http.cookiejar.CookieJar() opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj)) opener.addheaders = [(‘User-Agent‘, ‘Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36‘), (‘Cookie‘, ‘4564564564564564565646540‘)] urllib.request.install_opener(opener) try: while True: page = urllib.request.urlopen(url) html = page.read().decode("utf-8") page.close() anti_spider = re.findall(r‘403 Forbidden‘, html) if anti_spider: print("反爬虫了,休息10分钟...") time.sleep(600) else: return html except Exception as e: print(e) sys.exit()
对这一个网页中进行正则提取。在提取的过程中,我发现只能看到书籍的链接和书名,其他内容比如作者名和评分什么的,都是通过js动态生成的。噗。那就只好通过链接来获得每一本书的作者和评分了。
def get_books_info(html): """ 获得一页上面所有书的信息 """ books = [] one_page_books = re.findall(r‘href="(https://book\.douban\.com/subject/\d+/)" title="(.*?)"‘, html) for url, name in one_page_books: one_book_html = get_html(url) # print one_book_html author, score = get_book_info(one_book_html) # print author, score if not author and not score: break name = "《" + name + "》" print(‘{"name": %s, "author": %s, "score": %s, "url": %s}‘ % (name, author, score, url)) books.append({"name": name, "author": author, "score": score, "url": url}) # print("别爬太快,休息一下") time.sleep(3) return books
有一件很重要的事应该要说一下,做爬虫,一定要记得sleep,不仅仅是伪装人类行为迷惑反爬虫,也是对网站的尊重。互联网上各种网站的访问已经有90%是爬虫了,其中大部分都是搜索引擎,我们就不要再给人家网站添麻烦了——处理你家爬虫是要消耗服务器资源的,连续不停的爬是不文明的行为!我在开始的时候没有sleep,结果在调试的过程中爬了几百个网页,然后被反爬虫了。好吧,应该向豆瓣道个歉。人家已经很客气了,让我放肆的爬了几百本书,才把我封掉,而且豆瓣本身提供了给爬虫使用的API,我还自己费劲的写正则。
下面是从一本书籍的页面中获得作者和评分:
def get_book_info(html): """ 获得一本书的评分和作者 """ try: score = re.findall(r‘property="v:average"> (.*?) </strong>‘, html)[0] author = re.findall(r‘<span class="pl"> 作者</span>[\w\W]*?<a class="" href=".*?">(.*?)</a>‘, html)[0] return author, score except Exception as e: print("评分和作者出了点小问题:") print(e) return "", ""
这段没什么好说的,全是偷懒写法的正则。
把上面的几个函数组合起来:
def main(): page = 0 books = [] while True: url = "https://book.douban.com/tag/%%E5%%B0%%8F%%E8%%AF%%B4?start=%s&type=T" % str(page) html = get_html(url) one_page_books = get_books_info(html) books.extend(one_page_books) if len(one_page_books) == 0: print("好啦只有这些书啦~") break page += 20 # 有的书评价人数太少,没有分数,怕信息不全,补全一下 for b in books: b["author"] = b["author"] or "(佚名)" b["score"] = b["score"] or "0.0" # 排序 books.sort(key=lambda x: float(x["score"]), reverse=True) # 保存书籍信息 with open("douban.csv", "w", encoding="gbk", errors="ignore") as f: line_template = "%(name)s,%(author)s,%(score)s,%(url)s\n" f.write(line_template % ({"name": "书名", "author": "作者", "score": "豆瓣评分", "url": "豆瓣链接"})) for book in books: f.write(line_template % book)
最后在open的时候其实遇到了点小麻烦:网页是utf-8编码的,我爬下来网页之后就直接decode("utf-8")了。但是在windows下,open("douban.csv", "w")函数默认使用的中文编码方式是gbk,于是write时候就直接抛了个中文编码的异常。查到这个原因之后,我就加上了encoding="utf-8"这个参数。这回倒是成功保存了,但是csv默认用excel打开时还是乱码呀!因为excel使用gbk来打开的。倒是可以手动先用记事本打开,另存为选择ANSI来解决,但是我表示不屑于用这种方式,在群里问了问,有同学告诉我用ignore,燃鹅,在python3里面还encode/decode写一串,不是丑死!于是再百度,终于查到在python3中,open函数里面就可以直接加errors="ignore"这个参数。
搞定~最后的效果:
就酱。代码已上传github(https://github.com/anpengapple/doubanbooks)。有需要的同学拿去玩~其实还是不完美:第一,没有小说的详细类型;第二,只有小说而没有别的书;第三,没有直接保存excel,还得自己排版。将来有闲心可以再改改。(话说一直没学写excel也是因为懒,感觉全是体力劳动,完全没有技巧可言,不好玩)。