聚沙成塔--爬虫系列(九)(落地生根)

2017-10-29

版权声明:本文为作者原创文章,可以随意转载,但必须在明确位置标明出处!!!

上一章我们讲了类的概念,专业术语叫OOP(面向对象的编程),同时也讲了类的三大属性,「封装」、「继承」、「多态」,并且也用代码实现了继承和多态,让初学者更能直观的去理解继承和多态究竟是怎么回事的。相信认真阅读过的同学对类已经有了一个初步的认识。那么本章将会把上一篇文章的代码使用面向对象的编程去改写它。但这不是本章的重点,本章的重点是把我们的数据保存到磁盘上,专业术语叫「持久化」

持久化

将数据写到磁盘文件,也就是普通的文本文件是本章的主题,也是最简单的持久化方案,但是简单文件的存储不便于我们查找和统计,所以后面的章节我们将会讲到把数据存储到Excel、数据库中。

###内建函数open()
open内建函数返回一个文件对象,如果文件对象不存在,则抛出一个OSError异常。文件对象不仅可以用来访问普通的磁盘文件,它也可以访问其它抽象层面的文件,像网络文件,你也可以使用open打开一个url来读取页面的元素。

open函数的定义

open(file, mode=’r’, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

  • file: file是一个类似路径的对象,给出要打开的文件的路径名,可以是绝对路径也可以是相对于当前工作目录的路径
    • mode: 模式,就是以什么样的方式去打开文件。
    • buffering:缓冲是用于设置缓冲策略的可选整数。 通过0以切换缓冲(仅允许在二进制模式下),1选择行缓冲(仅在文本模式下可用),整数> 1表示固定大小的块缓冲区的大小(以字节为单位),该参数大于零的意思是对文件对象的读写操作为当前设置的值
    • encodeing: 编码,意思是以什么样的编码方式去读写文件,你可以设置该参数为‘utf-8’、‘gbk’等编码方式
    • errors: error参数是一个可选字符串,指定如何处理编码和解码错误,但是在二进制模式模式下无效
    • newline: 通用换行符控制,它的作用是用来帮助我们访问不同平台的文件,不同平台用来表示结束符号是不同的,如\r,\n,或者\r\n
    • closefd: 如果closefd为False,并且提供了文件描述符而不是文件名,则当文件关闭时,底层文件描述符将保持打开状态。 如果给定文件名,则closefd必须为True(默认值),否则会引发错误。
    • opener: 自定义打开器。

文件的打开模式

符号 说明
‘r’ open for reading (default)
‘w’ open for writing, truncating the file first
‘x’ open for exclusive creation, failing if the file already exists
‘a’ open for writing, appending to the end of the file if it exists
‘b’ binary mode
‘t’ text mode (default)
‘+’ open a disk file for updating (reading and writing)
‘U’ universal newlines mode (deprecated)

模式之间都可以通过‘+’符号来组合,‘r+w’打开一个可读又可写的文件对象,open默认打开模式为可读。

从文件读

open打开一个已存在的文件后,我们需要从文件里读取文件内容,读取文件内容有以下几个函数

  • read()方法用来直接读取字节到字符串中, 最多读取给定数目个字节. 如果没有给定 size参数(默认值为 -1)或者 size 值为负, 文件将被读取直至末尾
  • readline()每次读取文件中的一行
  • readlines()将文件中的每一行都读取到一个list列表中。

    写入文件

    写入文件同样用的的文件对象依然是open内建函数返回的,写入文件提供了以下函数
  • write()和read,readline()函数相反,它把含有文本数据或二进制数据块的字符串写入到文件中去.
  • writelines()将一个列表的数据写入打开的文件中,参数一定要是一个list类型的

    文件内移动

    seek()可以在文件中移动文件指针到不同位置,如果你学过C语言那么就跟C语言的fseek函数是一样的,0默认位置表示从文件开头算起,1当前位置表示从当前位置算起,2表示从为文件末尾算起。

写入数据到磁盘文件

1
2
3
4
5
6
7
try:
fp = open('./test.txt', 'w')
for index in range(100):
fp.write('hello world' + '\n')
fp.close()
except Exception as e:
raise e

在当前路径下打开一个test.txt文件,如果当前路径下没有这个文件那么它将会在当前目录下创建这个文件。fp是我们打开文件返回的文件句柄对象,这个是系统资源,拿到这个文件对象了就可以对它进行相应的操作,操作完后一定要记得释放对象,因为这个对象是系统资源。w是操作文件的模式,如果我们想要每次在文件的末尾追加新写入的数据可以用模式‘a+’,执行程序后你将会在程序的当前目录下看到一个test.txt文件,打开文件可以看到文件写入了100行‘hello world’

从磁盘中读取文件

1
2
3
4
5
6
7
try:
fp = open('./test.txt', 'r')
for content in fp.readlines():
print(content)
fp.close()
except Exception as e:
raise e

如果以‘r’模式打开文件,若test.txt文件不存在于当前目录,那么程序将会抛出一个FileNotFoundError异常,执行程序可以后终端上会打印出100行‘hello world’。

with语句

with语句是个大神器啊, with语句的然而,with 语句的目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉, 而不是像 try-except-finally 那样仅仅简化代码使之易用,不过with语句只支持上下文管理协议的对象。这显然意味着只有内建了”上下文管理”的对象可以和with 一起工作,上下文管理协议本章不做详细介绍,看到这里的同学如果有兴趣可以自己在网上了解了解。既然with语句这么神奇了,那么我们的读写文件就可以写成如下形式。

1
2
3
4
5
6
7
8
#读文件
with open('./test.txt') as fp:
for content in fp.readlines():
print(content)
#写文件
with open('./test.txt', 'w') as fp:
fp.write('hello world' + '\n')

try-except语句没有了,释放资源的close函数也没有了,这些都被with语句做了,是不是再也不用担心忘记释放资源了。本章要讲的内容就到此介绍了,最后奉上改写后的源码

from urllib import request
from urllib import error
import re
import os


class Scheduler(object):

    def __init__(self, url, user_agent):
        self.url = url
        self.headers = {'User-Agent': user_agent}

    def read_html(self, codec):
        '''[read_html]

        [读取html页面内容]

        Arguments:
            url {[string]} -- [url地址]
            headers {[dict]} -- [用户代理,这里是一个字典类型]
            codec {[string]} -- [编码方式]

        Returns:
            [string] -- [页面内容]
        '''
        # 构建一个请求对象
        try:
            req = request.Request(self.url, headers=self.headers)
            # 打开一个请求
            response = request.urlopen(req)
            # 读取服务器返回的页面数据内容
            content = response.read().decode(codec)

            return content

        except error.URLError as e:
            print(e.reason)
            return None       

    def match_element(self, content, pattern):
        '''[match_element]

        [匹配元素]

        Arguments:
            content {[string]} -- [文本内容]
            pattern {[object]} -- [匹配模式]

        Returns:
            [list] -- [匹配到的元素]
        '''
        # 匹配所有用户信息

        userinfos = re.findall(pattern, content)

        return userinfos
    def write_file(self, content):
        with open('./qiubai.txt', 'a+') as fp:
            fp.write(content + '\n')

    def get_content(self):
        content = self.read_html('utf-8')
        pattern = re.compile(r'<div class="article block untagged mb15[\s\S]*?class="stats-vote".*?</div>', re.S)
        if content:
            userinfos = self.match_element(content, pattern)

            if userinfos:
                pattern = re.compile(r'<a href="(.*?)".*?<h2>(.*?)</h2>.*?<div class="content">(.*?)</div>.*?<i class="number">(.*?)</i>', re.S)
                picture = re.compile(r'<div class="thumb">.*?src="(.*?)"', re.S)
                for userinfo in userinfos:
                    item = self.match_element(userinfo, pattern)
                    pictures = self.match_element(userinfo, picture)
                    try:
                        if item:
                            userid, name, content, num = item[0]
                            # 去掉换行符,<span></span>,<br/>符号
                            userid = re.sub(r'\n|<span>|</span>|<br/>', '', userid)
                            name = re.sub(r'\n|<span>|</span>|<br/>', '', name)
                            content = re.sub(r'\n|<span>|</span>|<br/>', '', content)

                            if pictures:
                                path = './users/'
                                if not os.path.exists(path):
                                    os.makedirs(path)

                                request.urlretrieve('http:' + pictures[0], path + os.path.basename(pictures[0]))

                                print((userid, name, content, num, pictures[0]))
                                self.write_file(userid + '\t' + name + '\t' + content + '\t' + num + '\t' + pictures[0])
                            else:
                                print((userid, name, content, num ))
                                self.write_file(userid + '\t' + name + '\t' + content + '\t' + num)
                    except Exception as e:
                        print(e)

if __name__ == '__main__':
  url = 'https://www.qiushibaike.com'
  user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
  handle = Scheduler(url, user_agent)
  handle.get_content()

执行结果

执行结果

欢迎关注我的公众号:「爱做饭的老谢」,老谢一直在努力…

上一篇:聚沙成塔–爬虫系列(八)(物以「类」聚,人以群分)
下一篇:聚沙成塔–爬虫系列(十)(这一刻你觉得找个程序员男朋友真幸福)