大家好,本次为大家带来的是抓取爱问知识人的问题并将问题和答案保存到数据库的方法,涉及的内容包括:
在这之前,我们需要先配置一下环境,我的Python的版本为2.7,需要额外安装的库有两个,一个是Beautiful Soup,一个是MySQLdb,在这里附上两个库的下载地址,
Beautiful Soup MySQLdb
大家可以下载之后通过如下命令安装
1
|
python
setup
.
py
install
|
环境配置好之后,我们便可以开心地撸爬虫了
首先我们随便找一个分类地址,外语学习 – 爱问知识人,打开之后可以看到一系列的问题列表。
我们在这个页面需要获取的东西有:
总的页码数,每一页的所有问题链接。
接下来我们需要遍历所有的问题,来抓取每一个详情页面,提取问题,问题内容,回答者,回答时间,回答内容。
最后,我们需要把这些内容存储到数据库中。
其实大部分内容相信大家会了前面的内容,这里的爬虫思路已经融汇贯通了,这里就说一下一些扩展的功能
日志输出,我们要输出时间和爬取的状态,比如像下面这样:
[2015-08-10 03:05:20] 113011 号问题存在其他答案 我个人认为应该是樱桃沟很美的
[2015-08-10 03:05:20] 保存到数据库,此问题的ID为 113011
[2015-08-10 03:05:20] 当前爬取第 2 的内容,发现一个问题 百度有一个地方,花儿带着芳香,水儿流淌奔腾是什么意思 多多帮忙哦 回答数量 1
[2015-08-10 03:05:19] 保存到数据库,此问题的ID为 113010
所以,我们需要引入时间函数,然后写一个获取当前时间的函数
1
2
3
4
5
6
7
8
9
|
import
time
#获取当前时间
def
getCurrentTime
(
self
)
:
return
time
.
strftime
(
'[%Y-%m-%d %H:%M:%S]'
,
time
.
localtime
(
time
.
time
(
)
)
)
#获取当前时间
def
getCurrentDate
(
self
)
:
return
time
.
strftime
(
'%Y-%m-%d'
,
time
.
localtime
(
time
.
time
(
)
)
)
|
以上分别是获取带具体时间和获取日期的函数,在输出时,我们可以在输出语句的前面调用这函数即可。
然后我们需要将缓冲区设置输出到log中,在程序的最前面加上这两句即可
1
2
|
f_handler
=
open
(
'out.log'
,
'w'
)
sys
.
stdout
=
f_handler
|
这样,所有的print语句输出的内容就会保存到out.log文件中了。
爬虫爬取过程中可能出现各种各样的错误,这样会导致爬虫的中断,如果我们重新运行爬虫,那么就会导致爬虫从头开始运行了,这样显然是不合理的。所以,我们需要把当前爬取的页面保存下来,比如可以保存到文本中,假如爬虫中断了,重新运行爬虫,读取文本文件的内容,接着爬取即可。
大家可以稍微参考一下函数的实现:
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
|
#主函数
def
main
(
self
)
:
f_handler
=
open
(
'out.log'
,
'w'
)
sys
.
stdout
=
f_handler
page
=
open
(
'page.txt'
,
'r'
)
content
=
page
.
readline
(
)
start_page
=
int
(
content
.
strip
(
)
)
-
1
page
.
close
(
)
print
self
.
getCurrentTime
(
)
,
"开始页码"
,
start_page
print
self
.
getCurrentTime
(
)
,
"爬虫正在启动,开始爬取爱问知识人问题"
self
.
total_num
=
self
.
getTotalPageNum
(
)
print
self
.
getCurrentTime
(
)
,
"获取到目录页面个数"
,
self
.
total_num
,
"个"
if
not
start_page
:
start_page
=
self
.
total_num
for
x
in
range
(
1
,
start_page
)
:
print
self
.
getCurrentTime
(
)
,
"正在抓取第"
,
start_page
-
x
+
1
,
"个页面"
try
:
self
.
getQuestions
(
start_page
-
x
+
1
)
except
urllib2
.
URLError
,
e
:
if
hasattr
(
e
,
"reason"
)
:
print
self
.
getCurrentTime
(
)
,
"某总页面内抓取或提取失败,错误原因"
,
e
.
reason
except
Exception
,
e
:
print
self
.
getCurrentTime
(
)
,
"某总页面内抓取或提取失败,错误原因:"
,
e
if
start_page
-
x
+
1
<
start_page
:
f
=
open
(
'page.txt'
,
'w'
)
f
.
write
(
str
(
start_page
-
x
+
1
)
)
print
self
.
getCurrentTime
(
)
,
"写入新页码"
,
start_page
-
x
+
1
f
.
close
(
)
|
这样,不管我们爬虫中途遇到什么错误,妈妈也不会担心了
页面处理过程中,我们可能遇到各种各样奇葩的HTML代码,和上一节一样,我们沿用一个页面处理类即可。
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
|
import
re
#处理页面标签类
class
Tool
:
#将超链接广告剔除
removeADLink
=
re
.
compile
(
' )
#去除img标签,1-7位空格,
removeImg
=
re
.
compile
(
'
#删除超链接标签
removeAddr
=
re
.
compile
(
'
#把换行的标签换为\n
replaceLine
=
re
.
compile
(
' | |'
)
#将表格制表 替换为\t
|
replaceTD
=
re
.
compile
(
' '
)
|
#将换行符或双换行符替换为\n
replaceBR
=
re
.
compile
(
'
| ' )
#将其余标签剔除
removeExtraTag
=
re
.
compile
(
'<.*?>'
)
#将多行空行删除
removeNoneLine
=
re
.
compile
(
'\n+'
)
def
replace
(
self
,
x
)
:
x
=
re
.
sub
(
self
.
removeADLink
,
""
,
x
)
x
=
re
.
sub
(
self
.
removeImg
,
""
,
x
)
x
=
re
.
sub
(
self
.
removeAddr
,
""
,
x
)
x
=
re
.
sub
(
self
.
replaceLine
,
"\n"
,
x
)
x
=
re
.
sub
(
self
.
replaceTD
,
"\t"
,
x
)
x
=
re
.
sub
(
self
.
replaceBR
,
"\n"
,
x
)
x
=
re
.
sub
(
self
.
removeExtraTag
,
""
,
x
)
x
=
re
.
sub
(
self
.
removeNoneLine
,
"\n"
,
x
)
#strip()将前后多余内容删除
return
x
.
strip
(
)
|
我们可以用一段含有HTML代码的文字,经过调用replace方法之后,各种冗余的HTML代码就会处理好了。
比如我们这么一段代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
<
article
class
=
"article-content"
>
<
h2
>前言
<
/
h2
>
<
p
>最近发现
MySQL服务隔三差五就会挂掉,导致我的网站和爬虫都无法正常运作。自己的网站是基于
MySQL,在做爬虫存取一些资料的时候也是基于
MySQL,数据量一大了,
MySQL它就有点受不了了,时不时会崩掉,虽然我自己有网站监控和邮件通知,但是好多时候还是需要我来手动连接我的服务器重新启动一下我的
MySQL,这样简直太不友好了,所以,我就觉定自己写个脚本,定时监控它,如果发现它挂掉了就重启它。
<
/
p
>
<
p
>好了,闲言碎语不多讲,开始我们的配置之旅。
<
/
p
>
<
p
>运行环境:
<
strong
>
Ubuntu
Linux
14.04
<
/
strong
>
<
/
p
>
<
h2
>编写
Shell脚本
<
/
h2
>
<
p
>首先,我们要编写一个
shell脚本,脚本主要执行的逻辑如下:
<
/
p
>
<
p
>显示
mysqld进程状态,如果判断进程未在运行,那么输出日志到文件,然后启动
mysql服务,如果进程在运行,那么不执行任何操作,可以选择性输出监测结果。
<
/
p
>
<
p
>可能大家对于
shell脚本比较陌生,在这里推荐官方的
shell脚本文档来参考一下
<
/
p
>
<
p
>
<
a
href
=
"http://wiki.ubuntu.org.cn/Shell%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80"
data
-
original
-
title
=
""
title
=
""
>
Ubuntu
Shell
编程基础
<
/
a
>
<
/
p
>
<
p
>
shell脚本的后缀为
sh,在任何位置新建一个脚本文件,我选择在
/
etc
/
mysql
目录下新建一个
listen
.
sh
文件。
<
/
p
>
<
p
>执行如下命令:
<
/
p
>
|
经过处理后便会变成如下的样子:
1
2
3
4
5
6
7
8
9
10
11
|
前言
最近发现
MySQL服务隔三差五就会挂掉,导致我的网站和爬虫都无法正常运作。自己的网站是基于
MySQL,在做爬虫存取一些资料的时候也是基于
MySQL,数据量一大了,
MySQL它就有点受不了了,时不时会崩掉,虽然我自己有网站监控和邮件通知,但是好多时候还是需要我来手动连接我的服务器重新启动一下我的
MySQL,这样简直太不友好了,所以,我就觉定自己写个脚本,定时监控它,如果发现它挂掉了就重启它。
好了,闲言碎语不多讲,开始我们的配置之旅。
运行环境:
UbuntuLinux14
.
04
编写
Shell脚本
首先,我们要编写一个
shell脚本,脚本主要执行的逻辑如下:
显示
mysqld进程状态,如果判断进程未在运行,那么输出日志到文件,然后启动
mysql服务,如果进程在运行,那么不执行任何操作,可以选择性输出监测结果。
可能大家对于
shell脚本比较陌生,在这里推荐官方的
shell脚本文档来参考一下
UbuntuShell编程基础
shell脚本的后缀为
sh,在任何位置新建一个脚本文件,我选择在
/
etc
/
mysql目录下新建一个
listen
.
sh文件。
执行如下命令:
|
经过上面的处理,所有乱乱的代码都会被处理好了。
在这里,我们想实现一个通用的方法,就是把存储的一个个内容变成字典的形式,然后执行插入语句的时候,自动构建对应的sql语句,插入数据。
比如我们构造如下的字典:
1
2
3
4
5
6
7
8
|
#构造最佳答案的字典
good_ans_dict
=
{
"text"
:
good_ans
[
0
]
,
"answerer"
:
good_ans
[
1
]
,
"date"
:
good_ans
[
2
]
,
"is_good"
:
str
(
good_ans
[
3
]
)
,
"question_id"
:
str
(
insert_id
)
}
|
构造sql语句并插入到数据库的方法如下:
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
|
#插入数据
def
insertData
(
self
,
table
,
my_dict
)
:
try
:
self
.
db
.
set_character_set
(
'utf8'
)
cols
=
', '
.
join
(
my_dict
.
keys
(
)
)
values
=
'"," '
.
join
(
my_dict
.
values
(
)
)
sql
=
"INSERT INTO %s (%s) VALUES (%s)"
%
(
table
,
cols
,
'"'
+
values
+
'"'
)
try
:
result
=
self
.
cur
.
execute
(
sql
)
insert_id
=
self
.
db
.
insert_id
(
)
self
.
db
.
commit
(
)
#判断是否执行成功
if
result
:
return
insert_id
else
:
return
0
except
MySQLdb
.
Error
,
e
:
#发生错误时回滚
self
.
db
.
rollback
(
)
#主键唯一,无法插入
if
"key 'PRIMARY'"
in
e
.
args
[
1
]
:
print
self
.
getCurrentTime
(
)
,
"数据已存在,未插入数据"
else
:
print
self
.
getCurrentTime
(
)
,
"插入数据失败,原因 %d: %s"
%
(
e
.
args
[
0
]
,
e
.
args
[
1
]
)
except
MySQLdb
.
Error
,
e
:
print
self
.
getCurrentTime
(
)
,
"数据库错误,原因%d: %s"
%
(
e
.
args
[
0
]
,
e
.
args
[
1
]
)
|
这里我们只需要传入那个字典,便会构建出对应字典键值和键名的sql语句,完成插入。
我们将运行结果输出到了日志里,那么怎么查看日志呢?很简单,在这里提供两种方法
方法一:
PHP倒序输出所有日志内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<
html
>
<
head
>
<
meta
charset
=
"utf-8"
>
<
meta
http
-
equiv
=
"refresh"
content
=
"5"
>
<
/
head
>
<
body
>
$fp
=
file
(
"out.log"
)
;
if
(
$fp
)
{
for
(
$i
=
count
(
$fp
)
-
1
;
$i
>=
0
;
$i
--
)
echo
$fp
[
$i
]
.
"
" ;
}
?>
<
/
body
>
<
/
html
>
|
此方法可以看到所有的输入日志,但是如果日志太大了,那么就会报耗费内存太大,无法输出。为此我们就有了第二种方法,利用linux命令,输出后十行内容。
方法二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<
html
>
<
head
>
<
meta
charset
=
"utf-8"
>
<
meta
http
-
equiv
=
"refresh"
content
=
"5"
>
<
/
head
>
<
body
>
$ph
=
popen
(
'tail -n 100 out.log'
,
'r'
)
;
while
(
$r
=
fgets
(
$ph
)
)
{
echo
$r
.
"
" ;
}
pclose
(
$ph
)
;
?>
<
/
body
>
<
/
html
>
|
上面两种方法都是5秒刷新一次网页来查看最新的日志。
好了,闲言碎语不多讲,直接上源码了
1
|
spider
.
py
|
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
|
# -*- coding:utf-8 -*-
import
urllib
import
urllib2
import
re
import
time
import
types
import
page
import
mysql
import
sys
from
bs4
import
BeautifulSoup
class
Spider
:
#初始化
def
__init__
(
self
)
:
self
.
page_num
=
1
self
.
total_num
=
None
self
.
page_spider
=
page
.
Page
(
)
self
.
mysql
=
mysql
.
Mysql
(
)
#获取当前时间
def
getCurrentTime
(
self
)
:
return
time
.
strftime
(
'[%Y-%m-%d %H:%M:%S]'
,
time
.
localtime
(
time
.
time
(
)
)
)
#获取当前时间
def
getCurrentDate
(
self
)
:
return
time
.
strftime
(
'%Y-%m-%d'
,
time
.
localtime
(
time
.
time
(
)
)
)
#通过网页的页码数来构建网页的URL
|
1
|
page
.
py
|
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
|
# -*- coding:utf-8 -*-
import
urllib
import
urllib2
import
re
import
time
import
types
import
tool
from
bs4
import
BeautifulSoup
#抓取分析某一问题和答案
class
Page
:
def
__init__
(
self
)
:
self
.
tool
=
tool
.
Tool
(
)
#获取当前时间
def
getCurrentDate
(
self
)
:
return
time
.
strftime
(
'%Y-%m-%d'
,
time
.
localtime
(
time
.
time
(
)
)
)
#获取当前时间
def
getCurrentTime
(
self
)
:
return
time
.
strftime
(
'[%Y-%m-%d %H:%M:%S]'
,
time
.
localtime
(
time
.
time
(
)
)
)
#通过页面的URL来获取页面的代码
def
getPageByURL
(
self
,
url
)
:
try
:
request
=
urllib2
.
Request
(
url
)
response
=
urllib2
.
urlopen
(
request
)
return
response
.
read
(
)
.
decode
(
"utf-8"
)
except
urllib2
.
URLError
,
e
:
|
1
|
tool
.
py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#-*- coding:utf-8 -*-
import
re
#处理页面标签类
class
Tool
:
#将超链接广告剔除
removeADLink
=
re
.
compile
(
' )
#去除img标签,1-7位空格,
removeImg
=
re
.
compile
(
'
#删除超链接标签
removeAddr
=
re
.
compile
(
'
#把换行的标签换为\n
replaceLine
=
re
.
compile
(
' | |'
)
#将表格制表 替换为\t
|
replaceTD
=
re
.
compile
(
' '
)
|
#将换行符或双换行符替换为\n
replaceBR
=
re
.
compile
(
'
| ' )
#将其余标签剔除
removeExtraTag
=
re
.
compile
(
'<.*?>'
)
|
1
|
mysql
.
py
|
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
|
# -*- coding:utf-8 -*-
import
MySQLdb
import
time
class
Mysql
:
#获取当前时间
def
getCurrentTime
(
self
)
:
return
time
.
strftime
(
'[%Y-%m-%d %H:%M:%S]'
,
time
.
localtime
(
time
.
time
(
)
)
)
#数据库初始化
def
__init__
(
self
)
:
try
:
self
.
db
=
MySQLdb
.
connect
(
'ip'
,
'username'
,
'password'
,
'db_name'
)
self
.
cur
=
self
.
db
.
cursor
(
)
except
MySQLdb
.
Error
,
e
:
print
self
.
getCurrentTime
(
)
,
"连接数据库错误,原因%d: %s"
%
(
e
.
args
[
0
]
,
e
.
args
[
1
]
)
#插入数据
def
insertData
(
self
,
table
,
my_dict
)
:
try
:
self
.
db
.
set_character_set
(
'utf8'
)
cols
=
', '
.
join
(
my_dict
.
keys
(
)
)
values
=
'"," '
.
join
(
my_dict
.
values
(
)
)
sql
=
"INSERT INTO %s (%s) VALUES (%s)"
%
(
table
,
cols
,
'"'
+
values
+
'"'
)
try
:
result
=
self
.
cur
.
execute
(
sql
)
insert_id
=
self
.
db
.
insert_id
(
)
self
.
db
.
commit
(
)
#判断是否执行成功
if
result
:
return
insert_id
else
:
return
0
except
MySQLdb
.
Error
,
e
:
#发生错误时回滚
self
.
db
.
rollback
(
)
#主键唯一,无法插入
if
"key 'PRIMARY'"
in
e
.
args
[
1
]
:
print
self
.
getCurrentTime
(
)
,
"数据已存在,未插入数据"
else
:
print
self
.
getCurrentTime
(
)
,
"插入数据失败,原因 %d: %s"
%
(
e
.
args
[
0
]
,
e
.
args
[
1
]
)
except
MySQLdb
.
Error
,
e
:
print
self
.
getCurrentTime
(
)
,
"数据库错误,原因%d: %s"
%
(
e
.
args
[
0
]
,
e
.
args
[
1
]
)
|
数据库建表SQL如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
CREATE
TABLE
IF
NOT
EXISTS
`iask_answers`
(
`id`
int(11)
NOT NULL
AUTO_INCREMENT
COMMENT
'自增ID',
`
text`
text
NOT NULL
COMMENT
'回答内容',
`question_id`
int(18)
NOT NULL
COMMENT
'问题ID',
`answerer`
varchar(255)
NOT NULL
COMMENT
'回答者',
`
date`
varchar(255)
NOT NULL
COMMENT
'回答时间',
`is_good`
int(11)
NOT NULL
COMMENT
'是否是最佳答案',
PRIMARY
KEY
(`id`)
)
ENGINE
=InnoDB
DEFAULT
CHARSET
=utf8;
CREATE
TABLE
IF
NOT
EXISTS
`iask_questions`
(
`id`
int(11)
NOT NULL
AUTO_INCREMENT
COMMENT
'问题ID',
`
text`
text
NOT NULL
COMMENT
'问题内容',
`questioner`
varchar(255)
NOT NULL
COMMENT
'提问者',
`
date`
date
NOT NULL
COMMENT
'提问时间',
`ans_num`
int(11)
NOT NULL
COMMENT
'回答数量',
`url`
varchar(255)
NOT NULL
COMMENT
'问题链接',
PRIMARY
KEY
(`id`)
)
ENGINE
=InnoDB
DEFAULT
CHARSET
=utf8;
|
运行的时候执行如下命令即可
1
|
nohup
python
spider
.
py
&
|
代码写的不好,仅供大家学习参考使用,如有问题,欢迎留言交流。
我们把PHP文件和log文件放在同一目录下,运行PHP文件,便可以看到如下的内容:
小伙伴们赶快试一下吧。