Ea5ter's Bolg

python爬虫学习(二)

字数统计: 2.2k阅读时长: 9 min
2018/07/29 Share

0.00 引言

最近的爬虫学习主要关于:储存数据和文档读取。

1.00 储存数据

虽然爬取的信息在编译器中查看很方便,但随着数据的不断增多,将其整合到本地来分析就尤为的重要。

1.10 媒体文件

通过URL我们可以将网页上的一些文件保存到本地。在python2.x版本中,urllib库下的urlretrieve函数可以根据URL下载文件。
urlretrieve(url, path),这个函数有两个参数,一个是目标文件的url,一个是文件的保存路径path。通过以下代码可以将”菜鸟教程”主页下的图片全部下载到本地的images文件夹下。

1
2
3
4
5
6
7
8
9
10
11
12
13
# _*_ coding: utf-8 _*_
import requests
from bs4 import BeautifulSoup
from urllib import urlretrieve

html = requests.get("http://www.runoob.com/")
soup = BeautifulSoup(html.text, "html.parser")
imageLink = soup.find("div", {"class":"col middle-column-home"}).findAll("img")
i = 1

for link in imageLink:
urlretrieve(link['src'], "images/%d.png"%i)
i += 1

虽然这几行简单代码实现了我们的目的,但可以说是相当的不完整。这样处理有几个问题:
1、这个images文件夹必须手动来创建,很不智能。
2、这样下载的文件没有区别性,因为名字只是简单的数字。
当处理的文件一多还需要手动来分类处理,非常的不方便。再来看看”菜鸟教程”主页:

用下面的代码来实现:以教程名为文件名,板块名为文件夹名,最后再将这些文件夹保存在images文件夹下。

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
# _*_ coding: utf-8 _*_
import requests
from bs4 import BeautifulSoup
from urllib import urlretrieve
import os
import re

# 下载文件实验
def getPath(downLoadDir, blocklink, filename):
blockname = blocklink.find('h2').get_text()
blockname = re.sub(r"/|\\|:|\*|\"|<|>|\||\?", '_', blockname)
path = downLoadDir + '/' + blockname + '/' + filename + '.png'
diecrtory = os.path.dirname(path)

if not os.path.exists(diecrtory):
os.makedirs(diecrtory)
return path

downLoadDir = "images"
html = requests.get("http://www.runoob.com/")
html.encoding = "utf-8"
soup = BeautifulSoup(html.text, "html.parser")
blockLinks = soup.find("div", {"class":"col middle-column-home"}).findAll("div", {"class":re.compile("codelist codelist-desktop cate\d")})
i = 1

for blocklink in blockLinks:
for link in blocklink.findAll("a"):
url = link.find("img")["src"]
name = link.find("h4").get_text()
name = re.sub(r"/|\\|:|\*|\"|<|>|\||\?", '_', name)
urlretrieve(url, getPath(downLoadDir, blocklink, name))

最后的效果如下:

这段代码有几个要点:
1、生成文件时要注意,文件名不能包含有特殊字符(\ / : * ? “ < > |)。所以这里用正则表达式:

1
re.sub(r"/|\\|:|\*|\"|<|>|\||\?", '_', name) # 把这些特殊字符全部替换为"_"

2、这里用了os模块来获取下载文件的文件夹。贴一篇讲os.path模块的文章:python os.path模块常用方法详解

1
2
3
os.path.dirname() # 获取文件路径
os.path.exists() # 判断路径是否存在
os.makedirs() # 生成文件路径

当然这段代码还是有点瑕疵。比如下载用的url是否可以访问,这里刚好每个url都可以访问,所以不用进行修饰,但如果给的是相对路径这类不能直接访问的,就要对每个url处理成可以访问的直接路径;另外我们的文件都是明确的.png文件,所以我手动加的后缀,但如果是.jpg或者根本不是图片文件,那还要根据情况来判断文件类型。。
最后贴上一段书上处理url的函数供参考,我们还是要根据目的来随机应变。

1
2
3
4
5
6
7
8
9
10
11
12
def getAbsoluteURL(baseurl, source):
if source.startswith("http://www."):
url = "http://" + source[11:]
elif source.startswith("http://"):
url = source
elif source.startswith("ww."):
url = "http://" + source[4:]
else:
url = baseurl + "/" + source
if baseurl not in url:
return None
return url

1.20 爬虫与MySQL

MySQL是目前最受欢迎的开源关系型数据库管理系统。用数据库来收集、整理我们爬虫的数据其中有很多的妙处。

1.21 MySQL + python

MySQL的安装就不必细述了,phpstudy一套带走。
python自带的库是没有操作MySQL的,需要第三方库的支持。这里使用的是最有名的一个库 PyMySQL 。同样使用pycharm就可以方便的自动安装了。
下面展示下PyMySQL的基本操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# _*_ coding: utf-8 _*_
import pymysql

conn = pymysql.connect(host = '127.0.0.1', user = 'root', passwd = 'root', db = 'mysql') # 连接数据库
cur = conn.cursor() # 生成光标对象

# 数据库操作
cur.execute('ues scraping')
cur.execute('select * from pages')
print cur.fetchall()

# 关闭连接
cur.close()
conn.close()

连接/光标模式是数据库编程的常用模式。这个模式可以抽象成连接/窗口,在用MySQL命令行建立连接后,就可以在这个窗口执行命令,并且我们也可以同时打开多个窗口执行不同的命令。
至于其它的操作这里就不细述了,其实经常用命令行操作MySQL,使用这个模块也就不难,只要记点函数就可以了。在这贴一篇讲 PyMySQL 的文章:Python中操作mysql的pymysql模块详解
此外MySQL是无法储存中文的,这需要设置Unicode编码,但我不论是用书上的方法还是网上的方法都没有解决这个问题,先记在着,以后解决了会更新下。

1.22 MySQL中的六度分隔游戏

终于又到了最有趣的板块,从A页面打通一条到B的隧道。
这里就要使用MySQL来储存分析数据了,和之前那个疯狂的随便乱跳相比,我们将每次加载的url,以及跳转的起始储存到数据库中。
为了实现这一目的我们需要提前建立两张表,一张是用来记录每次加载的url的pages;一张是记录每次跳转起始的links,links有两个字段一个是fromPageId、toPageId,这两个数字分别对应着pages表中的id字段。
我们用以下代码在MySQL命令行中创建这两张表(当然是要先有一个数据库):

1
2
create table pages (id int not NULL AUTO_INCREMENT ,url varchar(255) not NULL,created timestamp not NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id));
create table links (id int not NULL AUTO_INCREMENT ,fromPageId INT NULL,toPageId INT NULL,created timestamp not NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id));

接下来就是就是六度分隔游戏啦!

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
# _*_ coding: utf-8 _*_
import requests
from bs4 import BeautifulSoup
import re
import pymysql

conn = pymysql.connect(host = '127.0.0.1', user = 'root', passwd = 'root', db = 'mysql') # 连接数据库
cur = conn.cursor() # 生成光标对象
cur.execute("use wikipadia")

def insertPageIfNotExists(url):
cur.execute("select * from pages where url = \"%s\"" %url)
if cur.rowcount == 0:
cur.execute("insert into pages (url) values (\"%s\")" %url)
conn.commit()
return cur.lastrowid
else:
return cur.fetchone()[0]

def insertLink(fromPageId, toPageId):
cur.execute("select * from links where fromPageId = %s and toPageId = %s" %(int(fromPageId), int(toPageId)))
if cur.rowcount == 0:
cur.execute("insert into links (fromPageId, toPageId) values (%s, %s)" %(int(fromPageId), int(toPageId)))
conn.commit()

pages = set()
# i = 0
def getLinks(pageUrl, recursionLevel):
global pages
# global i
# i += 1
# if i == 10:
# cur.close()
# conn.close()
# exit(0)
if recursionLevel > 4:
return
pageId = insertPageIfNotExists(pageUrl)
html = requests.get("http://en.wikipedia.org" + pageUrl)
soup = BeautifulSoup(html.text, "html.parser")
for link in soup.findAll("a", {"href":re.compile('^(/wiki/)')}):
insertLink(pageId, insertPageIfNotExists(link.attrs['href']))
if link.attrs['href'] not in pages:
# 遇到一个新页面,加入集合并搜索里面的词条链接
newPage = link.attrs['href']
pages.add(newPage)
getLinks(newPage, recursionLevel + 1)

getLinks("/wiki/Kevin_Bacon", 0)
cur.close()
conn.close()

这里我是直接照搬书上的代码,理解一下就好了。在这分享一下”抄”代码的心得:先从主函数开始抄,自定义函数先不具体内容,大概知道是什么用就往下继续,抄完一个函数先暂停理解下,然后再以此抄与这个函数有关联的下一个,最后抄完就理解了。
这段代码还没解决我们的六度分隔问题,但是这个搜索方式与深度搜索十分相似。从这也可以大概预知解决这个问题还是得用到搜索算法。注释掉的那段是我后面加上去的,以防止程序运行个几天还运行不完。运行结果如下:

2.00 读取文档

这一章我没有细看,因为处理情况感觉挺特殊的,要是以后撞到了再看吧。主要聊聊文档编码。

2.10 文档编码

虽然书上一直用的是urlopen,但由于之前做ctf写脚本,我一直用的是request模块。刚看书没注意,现在发现还是有点区别。
最明显的就是 BeautifulSoup 的使用上。

1
2
3
4
5
6
# urlopen使用BeautifulSoup
url_html = urlopen("http://en.wikipedia.org")
url_soup = BeautifulSoup(url_html)
# request使用BeautifulSoup
req_html = request.get("http://en.wikipedia.org")
req_soup = BeautifulSoup(req_html.text, "html.parser")

回到主题上,如果要爬取一个中文页面,打印出来的内容往往会有乱码。如果使用request模块访问,可以通过添加一行代码来设置网页的解码方式。

1
2
3
req_html = request.get("http://en.wikipedia.org")
req_html.encodeing = "utf-8"
req_soup = BeautifulSoup(req_html.text, "html.parser")

3.00 结语

到这里基础章节就结束了,很基础也很重要。

CATALOG
  1. 1. 0.00 引言
  2. 2. 1.00 储存数据
    1. 2.1. 1.10 媒体文件
    2. 2.2. 1.20 爬虫与MySQL
      1. 2.2.1. 1.21 MySQL + python
      2. 2.2.2. 1.22 MySQL中的六度分隔游戏
  3. 3. 2.00 读取文档
    1. 3.1. 2.10 文档编码
  4. 4. 3.00 结语