爬取云图网站图书详细信息


最近打算做一个图书管理系统,其中图书的详细信息通过爬虫程序爬取云图网站上所有图书的详细信息,有效避免了手动输入,节约了大量的时间。因为Python也是现学现用,程序还存在很多问题,但是目前的主要目的不是写爬虫,等后续有时间了,再来深入学习下Python,并完善我的爬虫程序。

爬取到的信息包括了每本书的:标题、作者、出版社、出版年、ISBN、简介、目录和图书缩略图。并且将这些信息保存在MySQL数据库中,如果有感兴趣的小伙伴可以留言给我哦。

为什么选择爬取云图网站呢?主要是因为我学艺不精(゚ー゚),而这个网站很好爬……好了,话不多说,让我们开始吧!

学习Python基本语法

既然使用Python写网页爬虫,那么学习Python基本语法肯定是少不了的啦!推荐学习菜鸟教程Python 基础教程,浅显易懂,很快可以上手。

学习爬虫的基本工作原理

了解一下爬虫的工作原理,参考别人写的爬虫程序,对写爬虫程序的一般流程有一个大概的了解。
参考网站:如何入门 Python 爬虫? Python爬虫入门教程

分析待爬取的页面

这个过程分为两步进行:首先通过图书介绍页面获取所有图书详情页的url,然后依次访问每个图书详情页,爬取需要的信息。下面进行详细分析:

第一步:循环500次得到每个页面上20本图书的url
进入云图官网,点击图书超链接,可以发现该页面包含20本图书介绍,将网页拉到底,发现一共有500张这样的页面,而且:
第一页的url是:
http://www.yuntu.io/book/list?pageNumber=1&keyword=&type=&searchType=view
第二页的url是:
http://www.yuntu.io/book/list?pageNumber=2&keyword=&type=&searchType=view
依次类推,每一页的url变化的是pageNumber,我们只要将页数作为变量循环500次,就可以得到每张页面上20本图书的url,将所有这些url保存到url.txt文本中。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import re
import urllib2
import urlparse


# 匹配出图书详情页的url并添加到crawl_queue中


def download(url):
print 'Downloading:', url
try:
html = urllib2.urlopen(url).read()
for link in get_links(html):
# check if link matches expected regex
if re.match('/book/[0-9]+', link):
# from absolute link
link = urlparse.urljoin('http://www.yuntu.io', link)
# check if have already seen this link
if link not in seen:
seen.add(link)
crawl_queue.append(link)
except urllib2.URLError as e:
print 'Download error:', e.reason


# 获取每个页面中的链接
# 返回值包含所有链接


def get_links(html):
"""Return a list of links from html"""
# a regular expression to extract all links from the webpage
# 匹配web页面中所有的<a>标签
webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE)
# list of all links from the webpage
return webpage_regex.findall(html)


# 程序开始
if __name__ == '__main__':
# 保存图书详情页的url
crawl_queue = []
# 保存已经爬取过的url
seen = set(crawl_queue)
# 每次遍历从打开的页面中匹配出图书详情页的url并添加到crawl_queue中
for page in range(1, 501):
url = 'http://www.yuntu.io/book/list?pageNumber=%d&keyword=&type=&searchType=view' % page
download(url)

print len(crawl_queue)
file = open("url.txt", "wb")
for url in crawl_queue:
file.write(str(url) + '\n')


第二步:爬取10000本图书的url,使用正则表达式匹配需要爬取的字段,并将爬取的信息保存在MySQL数据库中
这部分内容涉及的知识点主要有:正则表达式的运用,Python操作MySQL数据库,Python抓取网页中的图片并保存到本地。代码中包含了详细的注释。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import cookielib
import urllib2
import re
from mysql_operation import MySQL


#定义图书信息类


class Book:
"""保存图书的详细信息"""

def __init__(self, book_name, book_author, book_publisher, book_publish_date,
book_ISBN, book_introduction, book_content, book_image_url):
self.book_name = book_name
self.book_author = book_author
self.book_publisher = book_publisher
self.book_publish_date = book_publish_date
self.book_ISBN = book_ISBN
self.book_introduction = book_introduction
self.book_content = book_content
self.book_image_url = book_image_url

def displayBook(self):
print "书名: ", self.book_name, "\n作者: ", self.book_author, "\n出版社:", self.book_publisher, \
"\n出版日期:", self.book_publish_date, "\nISBN:", self.book_ISBN, "\n简介:", self.book_introduction, \
"\n目录:", self.book_content, "\n缩略图地址:", self.book_image_url



# 使用正则表达式获取图书详细信息
# 返回保存图书详细信息的book对象


def get_book_detail(url):
try:
html = urllib2.urlopen(url).read()
book_name = re.search(r'<div style=\"font-size: 20px;font-weight:bold;margin-bottom:13px;\">(.*?)</div>', html,
re.S).group(1)
book_author = re.search(r'<a href=\"/book/listByAuthor\?keyword=.*?\">(.*?)</a>', html, re.S).group(1)
# 这里获取到的出版社和出版日期是连在一起的,之间用逗号或者空格分隔
book_publisher_date = re.search(r'str = \"(.*?)\"', html, re.S).group(1)
# 判断是否匹配到出版社和出版时间
if book_publisher_date:
# 出版社
book_publisher = re.split(r'[,\s]\s*', book_publisher_date)[0]
# 出版日期
book_publish_date = re.split(r'[,\s]\s*', book_publisher_date)[1]
else:
book_publisher = ''
book_publish_date = ''

book_ISBN = re.search(r'url: \"/book/getErrata\.json\?mixedIsbn=(.*?)\"', html, re.S).group(1)
book_introduction = re.search(r'<p >\&nbsp\;\&nbsp\;(.*?)</p>', html, re.S).group(1)
book_content = re.search(r'<p id=\"book_catalog\">(.*?)</p>', html, re.S).group(1)
if not book_content:
book_content = ''
book_image_url = re.search(r'<img src=\"(http\://cover.yuntu.io/.*?/' + str(book_ISBN) + '\.jpg)\" >',
html, re.S).group(1)

book = Book(book_name, book_author, book_publisher, book_publish_date,
book_ISBN, book_introduction, book_content, book_image_url)

except urllib2.URLError as e:
print 'Download error:', e.reason
return book



# 抓取网页文件内容,保存到内存
# @url 欲抓取文件 ,path+filename


def get_file(url):
try:
cj = cookielib.LWPCookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

req = urllib2.Request(url)
operate = opener.open(req)
data = operate.read()
return data
except BaseException, e:
print e
return None


'''
保存文件到本地

@path 本地路径
@file_name 文件名
@data 文件内容
'''


def save_file(path, file_name, data):
if data == None:
return

file = open(path + file_name, "wb")
file.write(data)
file.flush()
file.close()


# 使用正则表达式获取图书详细信息
# 返回保存图书详细信息的book对象


def download(url):
print 'Downloading:', url
try:
if url not in seen:
seen.add(url)
# 获取图书详细信息
book = get_book_detail(url)
# 显示获取到的图书详细信息
book.displayBook()
if not book.book_name:
return
# 将图书信息保存在数据库中
# SQL 插入语句
sql = """INSERT INTO Book(BookName, BookImage, BookWriter, BookPublisher, BookPublicationDate, ISBN, BookAbstract, BookContent)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"""

param = (
book.book_name, "BookCover/" + book.book_ISBN + ".jpg", book.book_author, book.book_publisher, book.book_publish_date,
book.book_ISBN, book.book_introduction, book.book_content)
conn.insert(sql, param)
# 根据图书的ISBN号构建图书缩略图的名称
book_image_name = book.book_ISBN + ".jpg"
# 下载图书的缩略图
save_file("/root/BookCover/", book_image_name, get_file(book.book_image_url))
except urllib2.URLError as e:
print 'Download error:', e.reason


# 程序开始
if __name__ == '__main__':
# 获取数据库连接
conn = MySQL('127.0.0.1', 'username', 'password', 3306)
# 选择数据库
conn.selectDb('YourDatabaseName')

crawl_queue = []
# 保存已经爬取过的url
seen = set(crawl_queue)
# 打开保存图书详细信息的文本文件
file = open("url.txt")
# 计数
count = 0
# 循环迭代云图网站图书页面
for line in file.readlines()[count:]:
print "***************正在打印第%d行****************" % count
count += 1
download(line)
# 数据库提交,每获取一行的信息后就提交一次,
conn.commit()
# 关闭数据库
conn.close()

其中MySQL操作参考博客Python 封装MySQL类并根据需要做了一些改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# -*- coding: UTF-8 -*-

import MySQLdb

OperationalError = MySQLdb.OperationalError

class MySQL:
def __init__(self, host, user, password, port=3306, charset="utf8"):
self.host = host
self.port = port
self.user = user
self.password = password
self.charset = charset
try:
self.conn = MySQLdb.connect(host=self.host, port=self.port, user=self.user, passwd=self.password)
self.conn.autocommit(False)
self.conn.set_character_set(self.charset)
self.cur = self.conn.cursor()
except MySQLdb.Error as e:
print("Mysql Error %d: %s" % (e.args[0], e.args[1]))

def selectDb(self, db):
try:
self.conn.select_db(db)
except MySQLdb.Error as e:
print("Mysql Error %d: %s" % (e.args[0], e.args[1]))

def insert(self, sql, param):
return self.cur.execute(sql, param)

def commit(self):
self.conn.commit()

def close(self):
self.cur.close()
self.conn.close()


提问: 为什么不在获取了图书详情页的url后直接进行第二步,而是在获取了全部的10000(每页20个共500页)个url后才开始第二步?
回答: 因为我的程序在处理异常时还不够完善,如果直接处理的话,程序在爬取过程中遇到异常停止后,就不能从发生异常的那个url开始了。如果分两步进行的话,程序在遇到异常后,手动修改count值(加1),就可以跳过出现异常的url,接着从下一个url开始处理。

结语

通过自学Python到实现一个简单的爬虫程序,前后用了不到一周的时间,虽然现在有很多漏洞,但是基本满足我现阶段的需求。通过这个过程也告诉我,有目的的去做一件事是多么高效,完全没必要等精通了Python再去学习爬虫,而是可以在边写爬虫的过程中边学Python,这样学到的知识才是最实用的。

参考文档
Python 基础教程
在线正则表达式测试
Python爬虫入门教程
Python 封装MySQL类
如何入门 Python 爬虫?
python抓取网页中图片并保存到本地
《用Python写网络爬虫》