美文网首页有点意思菜鸟学Python我的Python自学之路
Python爬虫——爬取豆瓣读书信息并存入数据库

Python爬虫——爬取豆瓣读书信息并存入数据库

作者: 李作 | 来源:发表于2016-09-16 01:03 被阅读6256次

    这两天爬了豆瓣读书的十万条左右的书目信息,用时将近一天,现在趁着这个空闲把代码总结一下,还是菜鸟,都是用的最简单最笨的方法,还请路过的大神不吝赐教。
    第一步,先看一下我们需要的库:

    import requests                       #用来请求网页
    from bs4 import BeautifulSoup         #解析网页
    import time          #设置延时时间,防止爬取过于频繁被封IP号
    import re            #正则表达式库
    import pymysql       #由于爬取的数据太多,我们要把他存入MySQL数据库中,这个库用于连接数据库
    import random        #这个库里用到了产生随机数的randint函数,和上面的time搭配,使爬取间隔时间随机
    

    这个是豆瓣的网址:https://book.douban.com/tag/?view=type&icn=index-sorttags-all
    我们要从这里获取所有分类的标签链接,进一步去爬取里面的信息,代码先贴上来:

    import requests
    from bs4 import BeautifulSoup       #导入库
    
    url="https://book.douban.com/tag/?icn=index-nav"
    wb_data=requests.get(url)                #请求网址
    soup=BeautifulSoup(wb_data.text,"lxml")  #解析网页信息
    tags=soup.select("#content > div > div.article > div > div > table > tbody > tr > td > a")
         #根据CSS路径查找标签信息,CSS路径获取方法,右键-检查-copy selector,tags返回的是一个列表
    for tag in tags:    
           tag=tag.get_text()    #将列表中的每一个标签信息提取出来
           helf="https://book.douban.com/tag/"   
              #观察一下豆瓣的网址,基本都是这部分加上标签信息,所以我们要组装网址,用于爬取标签详情页
           url=helf+str(tag)  
           print(url)    #网址组装完毕,输出
    

    以上我们便爬取了所有标签下的网址,我们将这个文件命名为channel,并在channel中创建一个channel字符串,放上我们所有爬取的网址信息,等下爬取详情页的时候直接从这里提取链接就好了,如下:

    channel='''   
          https://book.douban.com/tag/小说     
          https://book.douban.com/tag/外国文学    
          https://book.douban.com/tag/文学    
          https://book.douban.com/tag/随笔     
          https://book.douban.com/tag/中国文学    
          https://book.douban.com/tag/经典    
          https://book.douban.com/tag/日本文学    
          https://book.douban.com/tag/散文    
          https://book.douban.com/tag/村上春树    
          https://book.douban.com/tag/诗歌    
          https://book.douban.com/tag/童话    
          https://book.douban.com/tag/杂文    
          https://book.douban.com/tag/王小波    
          https://book.douban.com/tag/儿童文学    
          https://book.douban.com/tag/古典文学    
          https://book.douban.com/tag/张爱玲    
          https://book.douban.com/tag/名著    
          https://book.douban.com/tag/余华    
          https://book.douban.com/tag/当代文学    
          https://book.douban.com/tag/钱钟书    
          https://book.douban.com/tag/鲁迅    
          https://book.douban.com/tag/外国名著    
          https://book.douban.com/tag/诗词    
          https://book.douban.com/tag/茨威格    
          https://book.douban.com/tag/米兰·昆德拉    
          https://book.douban.com/tag/杜拉斯    
          https://book.douban.com/tag/港台    
          https://book.douban.com/tag/漫画    
          https://book.douban.com/tag/绘本    
          https://book.douban.com/tag/推理    
          https://book.douban.com/tag/青春    
          https://book.douban.com/tag/言情    
          https://book.douban.com/tag/科幻    
          https://book.douban.com/tag/东野圭吾    
          https://book.douban.com/tag/悬疑    
          https://book.douban.com/tag/武侠    
          https://book.douban.com/tag/奇幻    
          https://book.douban.com/tag/韩寒    
          https://book.douban.com/tag/日本漫画    
          https://book.douban.com/tag/耽美    
          https://book.douban.com/tag/亦舒    
          https://book.douban.com/tag/三毛    
          https://book.douban.com/tag/安妮宝贝    
          https://book.douban.com/tag/网络小说    
          https://book.douban.com/tag/推理小说    
          https://book.douban.com/tag/郭敬明    
          https://book.douban.com/tag/穿越    
          https://book.douban.com/tag/金庸    
          https://book.douban.com/tag/轻小说    
          https://book.douban.com/tag/阿加莎·克里斯蒂    
          https://book.douban.com/tag/几米    
          https://book.douban.com/tag/魔幻    
          https://book.douban.com/tag/张小娴    
          https://book.douban.com/tag/幾米    
          https://book.douban.com/tag/青春文学    
          https://book.douban.com/tag/科幻小说    
          https://book.douban.com/tag/J.K.罗琳    
          https://book.douban.com/tag/高木直子    
          https://book.douban.com/tag/古龙    
          https://book.douban.com/tag/沧月    
          https://book.douban.com/tag/落落    
          https://book.douban.com/tag/张悦然    
          https://book.douban.com/tag/蔡康永    
          https://book.douban.com/tag/历史    
          https://book.douban.com/tag/心理学    
          https://book.douban.com/tag/哲学    
          https://book.douban.com/tag/传记    
          https://book.douban.com/tag/文化    
          https://book.douban.com/tag/社会学    
          https://book.douban.com/tag/艺术    
          https://book.douban.com/tag/设计    
          https://book.douban.com/tag/政治    
          https://book.douban.com/tag/社会    
          https://book.douban.com/tag/建筑    
          https://book.douban.com/tag/宗教    
          https://book.douban.com/tag/电影    
          https://book.douban.com/tag/数学    
          https://book.douban.com/tag/政治学    
          https://book.douban.com/tag/回忆录    
          https://book.douban.com/tag/思想    
          https://book.douban.com/tag/中国历史    
          https://book.douban.com/tag/国学    
          https://book.douban.com/tag/音乐    
          https://book.douban.com/tag/人文    
          https://book.douban.com/tag/人物传记    
          https://book.douban.com/tag/戏剧          
          https://book.douban.com/tag/生活    
          https://book.douban.com/tag/成长    
          https://book.douban.com/tag/励志    
          https://book.douban.com/tag/心理     
          https://book.douban.com/tag/摄影    
          https://book.douban.com/tag/女性    
          https://book.douban.com/tag/职场    
          https://book.douban.com/tag/美食    
          https://book.douban.com/tag/教育    
          https://book.douban.com/tag/游记    
          https://book.douban.com/tag/灵修    
          https://book.douban.com/tag/健康    
          https://book.douban.com/tag/情感    
          https://book.douban.com/tag/手工    
          https://book.douban.com/tag/养生    
          https://book.douban.com/tag/两性    
          https://book.douban.com/tag/人际关系    
          https://book.douban.com/tag/家居    
          https://book.douban.com/tag/自助游    
          https://book.douban.com/tag/经济学    
          https://book.douban.com/tag/管理    
          https://book.douban.com/tag/经济    
          https://book.douban.com/tag/商业    
          https://book.douban.com/tag/金融    
          https://book.douban.com/tag/投资    
          https://book.douban.com/tag/营销    
          https://book.douban.com/tag/创业    
          https://book.douban.com/tag/理财    
          https://book.douban.com/tag/广告       
          https://book.douban.com/tag/股票    
          https://book.douban.com/tag/企业史    
          https://book.douban.com/tag/策划    
          https://book.douban.com/tag/科普    
          https://book.douban.com/tag/互联网    
          https://book.douban.com/tag/编程    
          https://book.douban.com/tag/科学    
          https://book.douban.com/tag/交互设计    
          https://book.douban.com/tag/用户体验    
          https://book.douban.com/tag/算法    
          https://book.douban.com/tag/web    
          https://book.douban.com/tag/科技    
          https://book.douban.com/tag/UE    
          https://book.douban.com/tag/通信    
          https://book.douban.com/tag/交互         
          https://book.douban.com/tag/UCD    
          https://book.douban.com/tag/神经网络    
          https://book.douban.com/tag/程序              
         '''
    

    现在,我们开始第二个程序。

    QQ图片20160915233329.png

    标签页下每一个图片的信息基本都是这样的,我们可以直接从这里提取到标题,作者,出版社,出版时间,价格,评价人数,以及评分等信息(有些外国作品还会有译者信息),提取方法与提取标签类似,也是根据CSS路径提取。
    我们先用一个网址来实验爬取:

    url="https://book.douban.com/tag/科技"
    wb_data = requests.get(url)
    soup = BeautifulSoup(wb_data.text.encode("utf-8"), "lxml")
    tag=url.split("?")[0].split("/")[-1]    #从链接里面提取标签信息,方便存储
    detils=soup.select("#subject_list > ul > li > div.info > div.pub")  #抓取作者,出版社信息,稍后我们用spite()函数再将他们分离出来
    scors=soup.select("#subject_list > ul > li > div.info > div.star.clearfix > span.rating_nums")   #抓取评分信息
    persons=soup.select("#subject_list > ul > li > div.info > div.star.clearfix > span.pl")    #评价人数
    titles=soup.select("#subject_list > ul > li > div.info > h2 > a")   #书名
    #以上抓取的都是我们需要的html语言标签信息,我们还需要将他们一一分离出来
    for detil,scor,person,title in zip(detils,scors,persons,titles): 
                                  #用一个zip()函数实现一次遍历
    #因为一些标签中有译者信息,一些标签中没有,为避免错误,所以我们要用一个try来把他们分开执行   
          try:       
               author=detil.get_text().split("/",4)[0].split()[0]     #这是含有译者信息的提取办法,根据“/”  把标签分为五部分,然后依次提取出来
               yizhe= detil.get_text().split("/", 4)[1]            
               publish=detil.get_text().split("/", 4)[2]           
               time=detil.get_text().split("/", 4)[3].split()[0].split("-")[0]   #时间我们只提取了出版年份  
               price=ceshi_priceone(detil)        #因为价格的单位不统一,我们用一个函数把他们换算为“元”
               scoe=scor.get_text() if True else ""    #有些书目是没有评分的,为避免错误,我们把没有评分的信息设置为空    
               person=ceshi_person(person)      #有些书目的评价人数显示少于十人,爬取过程中会出现错误,用一个函数来处理
               title=title.get_text().split()[0]   
    #当没有译者信息时,会显示IndexError,我们分开处理
          except IndexError:      
                try:               
                     author=detil.get_text().split("/", 3)[0].split()[0]                
                     yizhe=""         #将detil信息划分为4部分提取,译者信息直接设置为空,其他与上面一样      
                     publish=detil.get_text().split("/", 3)[1]                
                     time=detil.get_text().split("/", 3)[2].split()[0].split("-")[0]                
                     price=ceshi_pricetwo(detil)               
                     scoe=scor.get_text() if True else ""                
                     person=ceshi_person(person)                
                     title=title.get_text().split()[0]       
                except (IndexError,TypeError):          
                      continue   
    #出现其他错误信息,忽略,继续执行(有些书目信息下会没有出版社或者出版年份,但是数量很少,不影响我们大规模爬取,所以直接忽略)
     except TypeError:      
           continue
    
    #提取评价人数的函数,如果评价人数少于十人,按十人处理
    def ceshi_person(person):  
         try:       
              person = int(person.get_text().split()[0][1:len(person.get_text().split()[0]) - 4])   
         except ValueError:       
               person = int(10)   
               return person
    
    #分情况提取价格的函数,用正则表达式找到含有特殊字符的信息,并换算为“元”
    def ceshi_priceone(price):   
            price = detil.get_text().split("/", 4)[4].split()    
            if re.match("USD", price[0]):      
                     price = float(price[1]) * 6   
            elif re.match("CNY", price[0]):       
                     price = price[1]  
            elif re.match("\A$", price[0]):       
                     price = float(price[1:len(price)]) * 6   
            else:       
                     price = price[0]    
           return price
    def ceshi_pricetwo(price):    
           price = detil.get_text().split("/", 3)[3].split()   
           if re.match("USD", price[0]):       
                   price = float(price[1]) * 6   
           elif re.match("CNY", price[0]):       
                   price = price[1]   
           elif re.match("\A$", price[0]):        
                   price = float(price[1:len(price)]) * 6    
           else:       
                   price = price[0]   
           return price
    
    

    实验成功后,我们就可以爬取数据并导入到数据库中了,以下为全部源码,特殊情况会用注释一一说明。

    import requests
    from bs4 import BeautifulSoup
    import time
    import re
    import pymysql
    from channel import channel   #这是我们第一个程序爬取的链接信息
    import random
    
    def ceshi_person(person):  
         try:       
              person = int(person.get_text().split()[0][1:len(person.get_text().split()[0]) - 4])   
         except ValueError:       
               person = int(10)   
         return person
    
    def ceshi_priceone(price):   
            price = detil.get_text().split("/", 4)[4].split()    
            if re.match("USD", price[0]):      
                     price = float(price[1]) * 6   
            elif re.match("CNY", price[0]):       
                     price = price[1]  
            elif re.match("\A$", price[0]):       
                     price = float(price[1:len(price)]) * 6   
            else:       
                     price = price[0]    
            return price
    
    def ceshi_pricetwo(price):    
           price = detil.get_text().split("/", 3)[3].split()   
           if re.match("USD", price[0]):       
                   price = float(price[1]) * 6   
           elif re.match("CNY", price[0]):       
                   price = price[1]   
           elif re.match("\A$", price[0]):        
                   price = float(price[1:len(price)]) * 6    
           else:       
                   price = price[0]   
           return price
    
    
    #这是上面的那个测试函数,我们把它放在主函数中
    def mains(url):
          wb_data = requests.get(url)
          soup = BeautifulSoup(wb_data.text.encode("utf-8"), "lxml")
          tag=url.split("?")[0].split("/")[-1]    
          detils=soup.select("#subject_list > ul > li > div.info > div.pub")  
          scors=soup.select("#subject_list > ul > li > div.info > div.star.clearfix > span.rating_nums")   
          persons=soup.select("#subject_list > ul > li > div.info > div.star.clearfix > span.pl")    
          titles=soup.select("#subject_list > ul > li > div.info > h2 > a")   
          for detil,scor,person,title in zip(detils,scors,persons,titles):
                l = []  #建一个列表,用于存放数据
                try:       
                     author=detil.get_text().split("/",4)[0].split()[0] 
                     yizhe= detil.get_text().split("/", 4)[1]            
                     publish=detil.get_text().split("/", 4)[2]           
                     time=detil.get_text().split("/", 4)[3].split()[0].split("-")[0]   
                     price=ceshi_priceone(detil)  
                     scoe=scor.get_text() if True else ""     
                     person=ceshi_person(person)   
                     title=title.get_text().split()[0]   
                except IndexError:      
                      try:               
                           author=detil.get_text().split("/", 3)[0].split()[0]                
                           yizhe=""          
                           publish=detil.get_text().split("/", 3)[1]                
                           time=detil.get_text().split("/", 3)[2].split()[0].split("-")[0]                
                           price=ceshi_pricetwo(detil)               
                           scoe=scor.get_text() if True else ""                
                           person=ceshi_person(person)                
                           title=title.get_text().split()[0]       
                      except (IndexError,TypeError):          
                            continue   
    
           except TypeError:      
                 continue
           l.append([title,scoe,author,price,time,publish,person,yizhe,tag])
                        #将爬取的数据依次填入列表中
     
    
           sql="INSERT INTO allbooks values(%s,%s,%s,%s,%s,%s,%s,%s,%s)"  #这是一条sql插入语句
           cur.executemany(sql,l)   #执行sql语句,并用executemary()函数批量插入数据库中
           conn.commit()
    
    #主函数到此结束
    
    
    # 将Python连接到MySQL中的python数据库中
    conn = pymysql.connect( user="root",password="123123",database="python",charset='utf8')
    cur = conn.cursor()
    
    cur.execute('DROP TABLE IF EXISTS allbooks')   #如果数据库中有allbooks的数据库则删除
    sql = """CREATE TABLE allbooks(      
            title CHAR(255) NOT NULL,      
            scor CHAR(255),      
            author CHAR(255),     
            price CHAR(255),     
            time CHAR(255),    
            publish CHAR(255),     
            person CHAR(255),     
            yizhe CHAR(255),     
            tag CHAR(255)       
     )"""
    cur.execute(sql)  #执行sql语句,新建一个allbooks的数据库
    
    
    start = time.clock()   #设置一个时钟,这样我们就能知道我们爬取了多长时间了
    for urls in channel.split():    
          urlss=[urls+"?start={}&type=T".format(str(i)) for i in range(0,980,20)]   #从channel中提取url信息,并组装成每一页的链接
          for url in urlss:       
                 mains(url)       #执行主函数,开始爬取
                 print(url)        #输出要爬取的链接,这样我们就能知道爬到哪了,发生错误也好处理
                 time.sleep(int(format(random.randint(0,9))))   #设置一个随机数时间,每爬一个网页可以随机的停一段时间,防止IP被封
    end = time.clock()
    print('Time Usage:', end - start)    #爬取结束,输出爬取时间
    count = cur.execute('select * from allbooks')
    print('has %s record' % count)       #输出爬取的总数目条数
    
    # 释放数据连接
    if cur:   
         cur.close()
    if conn:   
         conn.close()
    

    这样,一个程序就算完成了,豆瓣的书目信息就一条条地写进了我们的数据库中,当然,在爬取的过程中,也遇到了很多问题,比如标题返回的信息拆分后中会有空格,写入数据库中会出现错误,所以只截取了标题的第一部分,因而导致数据库中的一些书名不完整,过往的大神如果有什么办法,还请指教一二。
    等待爬取的过程是漫长而又欣喜的,看着电脑上一条条信息被刷出来,成就感就不知不觉涌上心头;然而如果你吃饭时它在爬,你上厕所时它在爬,你都已经爬了个山回来了它还在爬时,便会有点崩溃了,担心电脑随时都会坏掉(还是穷学生换不起啊啊啊啊~)
    所以,还是要好好学学设置断点,多线程,以及正则,路漫漫其修远兮,吾将上下而求索共勉

    相关文章

      网友评论

      • 薛定谔的小白:楼主,那个,我想问下为什么要写两个货币换算的函数?还有就是刚才我去看了豆瓣的源码,发现现在的价格好像都是。。。元了。?!
        181993e60b29:教练您还在吗?我想学这个!
        薛定谔的小白:@李作 原来如此 谢谢大佬
        李作:@薛定谔的小白 价格写两个提取函数是因为爬到到价格在字串里的位置不一样,按照位置分的,,,我这个程序还是去年写的,后来我看豆瓣好像改版了눈_눈
      • 4863f458a886:非常感谢分享。很崇拜楼主。
        发现2个小错误:
        最后一个插Mysql代码中
        16行:def ceshi_priceone(price): 应改为:def ceshi_priceone(detil):
        28行:def ceshi_pricetwo(price): 应改为:def ceshi_pricetwo(detil):
        李作: @wqiang118 哇哦,你才是让我佩服的,可以看那么仔细😃加油~
      • 522954ca0239:楼主,其实你译者信息和价格之类,可以再做一个函数,直接进去每本书的详情页,这样子就不需要那么复杂的提取了。当然,消耗的时间会比较长,不过代码可读性就很好。
        李作:@活着就是造孽 好机智ԅ(¯ㅂ¯ԅ)IP被封可以设置个代理IP池的,我那么做其实就是偷懒-_-||
        522954ca0239:@李作 我觉得,关于译者信息之类,博主可以爬下来之后再利用其它的脚本对数据进行处理。顺便,今天忘了做保护被封IP,觉得博主那个 随机停几秒钟很精巧。
        李作: @活着就是造孽 当时是想进详情页里面爬的,但是爬了一页出来乱码,解决半天没解决掉,就偷懒在外面爬了(눈_눈)
      • 522954ca0239:请问楼主,爬取下来的 title 会出现空格的问题解决了么?似乎是因为网页之中的 css 样式导致的。
        李作: @活着就是造孽 我好久都没有看过这段代码了,,,你可以试一下replace这个函数,希望有帮助~( ̄▽ ̄~)~
      • eb02066fe549:我就提几点问题:
        1.yizhe= detil.get_text().split("/", 4)[1] 这个可能会抓取到出版社信息,怎么准确定位的了;
        2. if True else ''是废话,恒为True;
        3. function pricetwo多余。
        李作: @pring 闭上眼睛就想起来了,,,那个TURE其实是指前边的get_text,如果有内容的话就执行,不然就是空
        李作: @pring 最后的最后,小女子学艺不精,还请大神多多指教(认真脸)
        李作: @pring 李作: @pring 1.关于抓取到出版社信息,最后确实会有这种情况,还没有好的解决办法,只能在数据处理的时候解决了,,,其实我在处理时直接删掉了-_-||
        2.if TURE那个,是我太老了吗,找了几遍都没有找到
        3.priceone和pricetwo主要是针对对detail切分次数不同而设计的,如果在主函数里面就把price处理好了,他确实是多余的了-_-||
      • 鸭梨山大哎:关注!
        李作: @鸭梨山大哎 终于等到你~好开心😆

      本文标题:Python爬虫——爬取豆瓣读书信息并存入数据库

      本文链接:https://www.haomeiwen.com/subject/lrwbettx.html