zoukankan      html  css  js  c++  java
  • Python爬取豆瓣电子书信息

      最近写了个爬取豆瓣电子书信息的爬虫(常来豆瓣看书评和影评。。),爬取的站点是https://read.douban.com/kind/100?start=%s&sort=hot&promotion_only=False&min_price=None&max_price=None&works_type=None

    使用的是requests库来获得html(超级好用有木有!自带的urllib相比之下还比较麻烦。。)

    网站结构清晰,使用xpath(lxml库)解析,感觉和BeautifulSoup用起来差不多,但BS是基于DOM的,它会载入整个文档,解析整个

    DOM树,内存和时间开销会比xpath大很多,并且BS是python编写的,lxml是用c编写的。大家都说BS更好用,但我觉得易用性差不多

    啊, 所以果断数据量稍微大点我就用xpath了。

    xpath教程在这里:http://www.w3school.com.cn/xpath/index.asp

    数据库使用mysql,数据库驱动是mysql-connector。

      我将代码分为两部分:数据的获取与数据的存储,分别用两个类表示。

    数据获取:

     1 class ParsePageThread(threading.Thread):
     2     '''用于解析每个页面的线程,每个页面包含20项'''
     3     def __init__(self, url, queue=connector_queue, headers=None, host="https://read.douban.com"):
     4         super(ParsePageThread, self).__init__()
     5         self.url = url
     6         self.headers = headers
     7         self.host = host
     8         self.queue = queue
     9         self.res = [] 
    10 
    11     def run(self):
    12         self.getBookContent()
    13         print(self.res)
    14         self.queue.put(self.res)
    15 
    16     def __getHTMLText(self,url):
    17         try:
    18             r = requests.get(url, self.headers)
    19             r.raise_for_status()
    20             return r.text
    21         except:
    22             return ''
    23 
    24     def getBookContent(self):
    25         text = self.__getHTMLText(self.url)
    26         html = etree.HTML(text)
    27         book_urls = html.xpath('//li[@class="item store-item"]/div[@class="info"]/div[@class="title"]/a/@href')
    28         for book_url in book_urls: 
    29             url = self.host + book_url
    30             text = self.__getHTMLText(url)
    31             html = etree.HTML(text)
    32             name = html.xpath('//h1[@class="article-title"]/text()')
    33             if not name:
    34                 name = html.xpath('//h1[@itemprop="name"]/text()')
    35             author = html.xpath('//a[@class="author-item"]/text()')
    36             price = html.xpath('//span[@class="current-price-count"]/text()')
    37             if not price:
    38                 price = html.xpath('//span[@class="discount-price current-price-count"]/text()')
    39             press = html.xpath('//a[@itemprop="provider"]/text()')
    40             if not press:
    41                 press = html.xpath('//div[@class="provider"]/a/text()')
    42             words = html.xpath('//span[@class="labeled-text"]/text()')
    43             if not words:
    44                 words = ['unknown']
    45             word = words[1] if len(words) > 1 else words[0]
    46             try:
    47                 self.res.append([str(name[0]), str(author[0]), 
    48                         str(price[0]), str(press[0]), str(word)])
    49             except:
    50                 pass

    这里使用了多线程解析每个页面。

    数据存储使用mysql,因为要频繁操作数据库,这里使用数据库连接池以减小连接数据库的开销。

     1 class MySQLPool(object):
     2     '''MySql连接池类以减小用于请求连接、创建连接和关闭连接的开销'''
     3     def __init__(self, host='localhost', port='3306', user='root', password='password',
     4             database='test', pool_name='douban', pool_size=5):
     5         res = {}
     6         # self._content = content
     7         self._host = host
     8         self._port = port
     9         self._user = user
    10         self._password = password
    11         self.database = database
    12         res['host'] = self._host
    13         res['port'] = self._port
    14         res['user'] = self._user
    15         res['password'] = self._password
    16         res['database'] = self.database
    17         self.dbconfig = res
    18         self.pool = self.create_pool(pool_name=pool_name, pool_size=pool_size)
    19 
    20     def insert(self, content):
    21         if not content :
    22             return
    23         #若有重复的id(书名),直接忽略
    24         sql_ = "insert ignore into douban (id, author, price, press, words) 
    25             values (%s, %s, %s, %s, %s)"
    26         for item in content:
    27             print("insert operating...
    ")
    28             self.execute(sql_, args=tuple(item))
    29 
    30     def create_pool(self, pool_name='douban', pool_size=5):
    31         pool = mysql.connector.pooling.MySQLConnectionPool(
    32                 pool_name=pool_name,
    33                 pool_size=pool_size,
    34                 pool_reset_session=True,
    35                 **self.dbconfig
    36                 )
    37         return pool
    38 
    39     def close(self, conn, cursor):
    40         cursor.close()
    41         conn.close()
    42 
    43     def execute(self, sql, args=None, commit=True):
    44         conn = self.pool.get_connection()
    45         cursor = conn.cursor()
    46         if args:
    47             cursor.execute(sql, args)
    48         else:
    49             cursor.execute(sql)
    50         if commit:
    51             conn.commit()
    52             self.close(conn, cursor)
    53             return None
    54         else:
    55             res = cursor.fetchall()
    56             self.close(conn, cursor)
    57             return res
    View Code

    细心的朋友应该看到了这里使用了queue用于在数据的获取和存储之间架起桥梁,因为queue自带锁,

    可保证线程间公共数据的安全,不过目前数据存储方面还没使用多线程。

    爬虫终于跑起来了,不过我预料之中的事也发生了:在爬取3000+数据的时候,连接断开了,大概是

    被豆瓣发现了?发现才是正常的。。

    我已经加入了user-agent和referer,后面考虑使用ip代理吧。如果不行就试试selenium,但那样又会

    降低爬取速度,加上我这龟速的网,想想都可怕。

    试完之后再来更新罢了。就酱,睡觉(~﹃~)~zZ

  • 相关阅读:
    MongoDB-JAVA-Driver 3.2版本常用代码全整理(4)
    MongoDB-JAVA-Driver 3.2版本常用代码全整理(3)
    MongoDB-JAVA-Driver 3.2版本常用代码全整理(2)
    MongoDB-JAVA-Driver 3.2版本常用代码全整理(1)
    c++清除输入缓冲区之 sync() vs ignore()
    typedef 类型重命名 和 #define 宏定义(1)
    从gcc的__attribute__((packed))聊到结构体大小的问题
    对于volatile的理解
    把一个string串的所有小写字母转成大写字母的例子来看看看全局函数的使用
    string与char* 互相转换以及周边问题
  • 原文地址:https://www.cnblogs.com/dinjufen/p/8094807.html
Copyright © 2011-2022 走看看