当当图书又打折?

  周六没啥事干,又不想把一切时间都投入到工作搞得看起来丝毫没有个人生活,于是在百无聊赖中找到了一个需求


我是一个图书重度爱好者,概括起来讲就是那种特别喜欢买书但永远都是 买的速度>>看的速度 ,最近当当开学季又搞促销满200减100,好,是时候出手买书了!不过有两个问题:

  • 我自己都不知道自己要买啥书
  • 既然不知道自己想买啥书,那就找一些经典书买吧,但在促销书中哪些才是经典书

  这种东西,如果要我手动打开一个当当促销页面,用人眼扫过页面上全部书,还要随手滚鼠标滚轮下拉页面,找到一些看起来带点名著feel的书去豆瓣搜评分来确认是否是好书,然后一个促销页展示60本书走完这样一个流程还要去手动翻页,最后还得忍耐当当页面的加载速度……想想就觉得生无可恋(一开始我还真的是这样:-D,不要笑我)。这么繁琐磨人的操作,最适合就是自动化啦:-D,于是就写了点python做了这个需求。

  程序的工作流程其实也已经说清楚了,就是
  • 从当当的促销页面爬下书名
  • 书名拿到豆瓣那里去查询,获取评分
  • 把评分汇总输出到一个excel中

直接飞过去代码那里
  再具体点,爬书名需要用到正则和网络库(我用requests库,也就是http for humans,urllib不好懂学习成本相对大);豆瓣查询那里用豆瓣提供的Api,具体自行搜索“豆瓣Api”;excel操作使用openpyxl,之前从来没用过这种库,也只是临时看一下别人对目前python的几个excel库的评价然后就挑了这个。


  那么作为一名软件测试人员,这里事后扯一扯,来看看这程序哪些地方可能出bug吧(其实写程序的时候我也根本就没想到那么多,一跑起来console各种飘红……)。从程序的流程来看,就三个大点,其中主要操作就是爬取网页、豆瓣查书和写入excel。

爬取网页

这个一看就知道是异常高发处,毕竟网络本身就是一个不稳定因素,更不要说还对python网络库底层代码零掌控(╯-_-)╯╧╧ (读过python urllib源码的请受我一拜)。另外,爬取下来的html也有编码的问题,我当时就直接decode成gbk,跑着跑着发现抛异常说一些字符竟然无法用gbk解码,后来代码就改成 response.content.decode('gbk', 'ignore'),这才绕过问题。不过当当网页爬取倒是没发生什么特别情况,毕竟对它请求也不频繁,一次请求下来60本书去豆瓣查费时都够呛,不需要担心当当封杀我ip。

豆瓣查书

这里用到的是豆瓣提供的Api,就照着它给的格式发请求和解析response。不过这里很容易就会因为请求过于频繁而被豆瓣封杀ip。我测试的时候请求量不算太多,豆瓣也没搞我,我还以为豆瓣很大度。后来真的跑起来了,看后台打log突然程序就终止了,“明明之前跑得还好好的呀,同样的代码循环一下怎么就出问题了?”不过很快就彩到是请求过于频繁,把豆瓣的response打印出来一看,果然是封杀了我的ip,这里也是整个程序性能的最大限制。(关于反爬虫这个帖子比较有意思https://www.zhihu.com/question/28168585)

写入excel

这里比较容易产生误操作,我自己出过的问题就是:
1、一直重复覆盖excel第一行,也就是搞来搞去只有一行数据写入。
2、忘记判断是否存在创建好了的excel,每次都去新建,新的excel把旧的excel覆盖
3、excel写入的列不整齐,反正就是写入后格式很混乱

  整体来说,代码功能也不怎么完善,结构不优雅,写法不美观,只是为了实现基本需求练练手,有兴趣的同学随便拿去改。有时候嘛,有想法就一定要自己去折腾,不要犹豫,一旦犹豫起来很容易就会放弃,然后就这样失去了一次锻炼机会。所以,不要把自己的灵光一现不当回事,谁也说不准这会不会改变你自己。

最后摆上代码:

# encoding:utf-8
# developed under python3.5
# @author: zingphoy
# @time: 2017.9.3
'''
背景: 当当的折扣活动如满200减100中出售的计算机书籍质量参差不齐,普遍多是诸如《30天从入门到精通》的神书,我们在没有特定买书需求的同时又想蹭蹭折扣活动买些以后可能看到的好书,而关键是我们又不知道哪些是好那些书是坏,所以需要去豆瓣查询在折扣中出售的书有哪些是好书,这里的目标就是要做到这种查询的自动化。
功能: 首先从当当某些页面抽取书本名字,然后从豆瓣中查找这些书的评分,最后返回一个默认排序为评分从高到低的excel表。
    暂时搜索不太准确,归因于豆瓣的搜索功能做了这么多年还是惨不忍睹...可以添加截取不同长度多次搜索或者根据Api调整count来取搜索的前count个结果,最后做权衡比较。
'''
import requests
import re
import time
from openpyxl import Workbook
from openpyxl import load_workbook


# 从当当活动页面爬去书籍名称存到bookList并返回
def spiderForDangdang(url):
    bookList = []
    response = requests.get(url)
    print('Here we get the response from dangdang')
    regex = '<li>.*?<p class="name"><a title="(.*?)".*?</a></p>.*?</li>'
    gbkContent = response.content.decode('gbk', 'ignore')
    print('Begin content matching')
    matchingContent = extractContent(gbkContent, regex)
    print('End of content matching')

    # 从当当取得的书名一般都非常长,因为其中附带了一些无用的推广描述,大概截取书名前面部分就可以了
    for book in matchingContent:
        book = book.strip()
        if book.__len__() > 15:
            book = book[0:14]
        bookList.append(book)
    return bookList


# 对content使用正则regex进行内容抽取
def extractContent(content, regex):
    pattern = re.compile(regex, re.S)
    # print(content)
    matchingContent = re.findall(pattern, content)
    return matchingContent


# 对传入的列表中的书籍逐一查询评分和评分人数,结果输入到ratingList并返回
def getDoubanRating(bookList):
    ratingList = []
    for book in bookList:
        queryUrl = 'https://api.douban.com/v2/book/search?q=' + book + '&count=1'  # 豆瓣提供的查询Api
        try:
            response = requests.get(queryUrl)
            attributes = response.json()['books']  # 具体参考豆瓣Api
            if attributes:
                attributes = attributes[0]
            else:
                print("\nCan't find a book named " + book)
                continue
                # print(type(attributes))
                # print(attributes)
            bookObject = []
            bookObject.append(attributes['title'] + attributes['subtitle'])
            bookObject.append(','.join(attributes['author']))  # 这里有个坑,作者可能有多个,所以这里是一个list
            bookObject.append(attributes['price'])
            bookObject.append(attributes['rating']['numRaters'])
            bookObject.append(attributes['rating']['average'])
            bookObject.append(book)  # 增加这一项是因为豆瓣搜索出来的书不一定正确,这一项可用来确认搜索结果

            ratingList.append(bookObject)
            time.sleep(10)
        except Exception as e:
            print(e)
            print('An error occurs while making a request to douban')
            return None
        print("\nNow it's dealing with --- " + book)

    return ratingList


# 传入最终结果,结果输出到
def exportToExcel(ratingList):
    if ratingList != None:
        try:
            wb = load_workbook('DangdangToDouban.xlsx')
            ws = wb['DangdangToDouban']
        except Exception as e:
            # 如果该excel不存在
            wb = Workbook()
            ws = wb.create_sheet('DangdangToDouban', 0)
            # 插入excel表头
            excelTop = ['title', 'author', 'price', 'number_Raters', 'rating', 'search_name']
            c = 1
            for top in excelTop:
                ws.cell(row=1, column=c).value = top
                c += 1
        if hasattr(ws, 'max_row'):
            r = ws.max_row  # 获取行数
        else:
            r = 0
        r += 1
        c = 1
        for bookObject in ratingList:
            print('converting into an Excel file...')
            # 往Excel写入数据
            for x in bookObject:
                ws.cell(row=r, column=c).value = x
                c += 1
            r += 1
            c = 1
        wb.save('DangdangToDouban.xlsx')
    else:
        print('There is no ratingList.')  # 遍历当当图书折扣活动的网页


def run(url):
    for i in range(1, 65):
        nextUrl = url.rsplit('1', 1)[0]
        nextUrl += str(i)
        bookList = spiderForDangdang(nextUrl)
        ratingList = getDoubanRating(bookList)
        if ratingList != None:
            exportToExcel(ratingList)
        else:
            print('Shutdown run() for accident')
            return


if __name__ == '__main__':
    dangdangUrl = r'http://promo.dangdang.com/6049690?category_id=01.54.00.00.00.00&page_index=1'  # 2017年9月 开学季当当满减活动
    run(dangdangUrl)
    print('\nHere comes to the end!~')

(回到前面)

posted @ 2017-09-04 00:18  拉丁看雪  阅读(1394)  评论(0编辑  收藏  举报