我的豆瓣短评爬虫的多线程改写

来源:互联网 发布:2017最近的网络流行语 编辑:程序博客网 时间:2024/05/29 06:50

对之前我的那个豆瓣的短评的爬虫,进行了一下架构性的改动。尽可能实现了模块的分离。但是总是感觉不完美。暂时也没心情折腾了。

同时也添加了多线程的实现。具体过程见下。

改动

独立出来的部分:

  • MakeOpener
  • MakeRes
  • GetNum
  • IOFile
  • GetSoup
  • main

将所有的代码都置于函数之中,显得干净了许多。(*^__^*) 嘻嘻……

使用直接调用文件入口作为程序的起点

if __name__ == "__main__":    main()

注意,这一句并不代表如果该if之前有其他直接暴露出来的代码时,他会首先执行。

print("首先执行")if __name__ == "__main__":    print("次序执行")# 输出如下:# 首先执行# 次序执行

if语句只是代表顺序执行到这句话时进行判断调用者是谁,若是直接运行的该文件,则进入结构,若是其他文件调用,那就跳过。

多线程

这里参考了【Python数据分析】Python3多线程并发网络爬虫-以豆瓣图书Top,和我的情况较为类似,参考较为容易。

仔细想想就可以发现,其实爬10页(每页25本),这10页爬的先后关系是无所谓的,因为写入的时候没有依赖关系,各写各的,所以用串行方式爬取是吃亏的。显然可以用并发来加快速度,而且由于没有同步互斥关系,所以连锁都不用上。

正如引用博文所说,由于问题的特殊性,我用了与之相似的较为直接的直接分配给各个线程不同的任务,而避免了线程交互导致的其他问题。

我的代码中多线程的核心代码不多,见下。

thread = []for i in range(0, 10):    t = threading.Thread(            target=IOFile,            args=(soup, opener, file, pagelist[i], step)        )    thread.append(t)# 建立线程for i in range(0, 10):    thread[i].start()for i in range(0, 10):    thread[i].join()

调用线程库threading,向threading.Thread()类中传入要用线程运行的函数及其参数。

线程列表依次添加对应不同参数的线程,pagelist[i]step两个参数是关键,我是分别为每个线程分配了不同的页面链接,这个地方我想了半天,最终使用了一些数学计算来处理了一下。

同时也简单试用了下列表生成式:

pagelist = [x for x in range(0, pagenum, step)]

这个和下面是一致的:

pagelist = []for x in range(0, pagenum, step):    pagelist.append(x)

threading.Thread的几个方法

值得参考:多线程

  • start() 启动线程
  • jion([timeout]),依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的join函数。在程序中,最后join()方法使得当所调用线程都执行完毕后,主线程才会执行下面的代码。相当于实现了一个结束上的同步。这样避免了前面的线程结束任务时,导致文件关闭。

注意

使用多线程时,期间的延时时间应该设置的大些,不然会被网站拒绝访问,这时你还得去豆瓣认证下"我真的不是机器人"(尴尬)。我设置了10s,倒是没问题,再小些,就会出错了。

完整代码

# -*- coding: utf-8 -*-"""Created on Thu Aug 17 16:31:35 2017@note: 为了便于阅读,将模块的引用就近安置了@author: lart"""import timeimport socketimport reimport threadingfrom urllib import parsefrom urllib import requestfrom http import cookiejarfrom bs4 import BeautifulSoupfrom matplotlib import pyplotfrom datetime import datetime# 用于生成短评页面网址的函数def MakeUrl(start):    """make the next page's url"""    url = 'https://movie.douban.com/subject/26934346/comments?start=' \        + str(start) + '&limit=20&sort=new_score&status=P'    return urldef MakeOpener():    """make the opener of requset"""    # 保存cookies便于后续页面的保持登陆    cookie = cookiejar.CookieJar()    cookie_support = request.HTTPCookieProcessor(cookie)    opener = request.build_opener(cookie_support)    return openerdef MakeRes(url, opener, formdata, headers):    """make the response of http"""    # 编码信息,生成请求,打开页面获取内容    data = parse.urlencode(formdata).encode('utf-8')    req = request.Request(                    url=url,                    data=data,                    headers=headers                )    response = opener.open(req).read().decode('utf-8')    return responsedef GetNum(soup):    """get the number of pages"""    # 获得页面评论文字    totalnum = soup.select("div.mod-hd h2 span a")[0].get_text()[3:-2]    # 计算出页数    pagenum = int(totalnum) // 20    print("the number of comments is:" + totalnum,          "the number of pages is: " + str(pagenum))    return pagenumdef IOFile(soup, opener, file, pagestart, step):    """the IO operation of file"""    # 循环爬取内容    for item in range(step):        start = (pagestart + item) * 20        print('第' + str(pagestart + item) + '页评论开始爬取')        url = MakeUrl(start)        # 超时重连        state = False        while not state:            try:                html = opener.open(url).read().decode('utf-8')                state = True            except socket.timeout:                state = False        # 获得评论内容        soup = BeautifulSoup(html, "html.parser")        comments = soup.select("div.comment > p")        for text in comments:            file.write(text.get_text().split()[0] + '\n')            print(text.get_text())        # 延时1s        time.sleep(10)    print('线程采集写入完毕')def GetSoup():    """get the soup and the opener of url"""    main_url = 'https://accounts.douban.com/login?source=movie'    formdata = {        "form_email": "your-email",        "form_password": "your-password",        "source": "movie",        "redir": "https://movie.douban.com/subject/26934346/",        "login": "登录"            }    headers = {        "User-Agent": "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1)\            Gecko/20061208 Firefox/2.0.0 Opera 9.50",        'Connection': 'keep-alive'            }    opener = MakeOpener()    response_login = MakeRes(main_url, opener, formdata, headers)    soup = BeautifulSoup(response_login, "html.parser")    if soup.find('img', id='captcha_image'):        print("有验证码")        # 获取验证码图片地址        captchaAddr = soup.find('img', id='captcha_image')['src']        # 匹配验证码id        reCaptchaID = r'<input type="hidden" name="captcha-id" value="(.*?)"/'        captchaID = re.findall(reCaptchaID, response_login)        # 下载验证码图片        request.urlretrieve(captchaAddr, "captcha.jpg")        img = pyplot.imread("captcha.jpg")        pyplot.imshow(img)        pyplot.axis('off')        pyplot.show()        # 输入验证码并加入提交信息中,重新编码提交获得页面内容        captcha = input('please input the captcha:')        formdata['captcha-solution'] = captcha        formdata['captcha-id'] = captchaID[0]        response_login = MakeRes(main_url, opener, formdata, headers)        soup = BeautifulSoup(response_login, "html.parser")    return soup, openerdef main():    """main function"""    timeout = 5    socket.setdefaulttimeout(timeout)    now = datetime.now()    soup, opener = GetSoup()    pagenum = GetNum(soup)    step = pagenum // 9    pagelist = [x for x in range(0, pagenum, step)]    print('pageurl`s list={}, step={}'.format(pagelist, step))    # 追加写文件的方式打开文件    with open('秘密森林的短评.txt', 'w+', encoding='utf-8') as file:        thread = []        for i in range(0, 10):            t = threading.Thread(                    target=IOFile,                    args=(soup, opener, file, pagelist[i], step)                )            thread.append(t)        # 建立线程        for i in range(0, 10):            thread[i].start()        for i in range(0, 10):            thread[i].join()    end = datetime.now()    print("程序耗时: " + str(end-now))if __name__ == "__main__":    main()

运行结果

效率有提升

对应的单线程程序在github上。单线程:

单线程.jpg

可见时间超过30分钟。修改后时间缩短到了11分钟。

多线程.jpg

文件截图

ScreenShot00080.jpg

我的项目

具体文件和对应的结果截图我放到了我的github上。

mypython

原创粉丝点击