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...\r")
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

posted @ 2017-12-23 23:26  暖花凉树  阅读(902)  评论(0编辑  收藏  举报