一 CSV文件
csv是逗号分隔值文件格式,一般用WORDPAD或记事本(NOTE),EXCEL打开。csv(逗号分隔值)是一种用来存储数据的纯文本文件,通常都是用于存放电子表格或数据的一种文件格式。这种格式的数据通常应用在数据处理方面,比如我们爬虫的数据量较小,不用插入数据库,或者只是临时测试,那么可以把数据存放在.csv文件中,很是方便。下面我们结合pythonCSV模块的源码讲解具体怎么用
要用到操作csv文件的库,要导入csv模块,我们进入到csv源码之中来看看。
“""
csv.py - read/write/investigate CSV files
"""
import re
from _csv import Error, __version__, writer, reader, register_dialect, \
unregister_dialect, get_dialect, list_dialects, \
field_size_limit, \
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
__doc__
from _csv import Dialect as _Dialect
from collections import OrderedDict
from io import StringIO
首先来看,csv的引入。可以看出它导入了_csv模块,那么也就是说,此模块的实现是有两层,最底层的方法实现是在_CSV模块中,CSV模块只是对于_csv模块的一个再次封装和功能强化。
来看具体用法,首先是读csv操作,csv模块有提供了两种方式来读,一种是csv.reader,另一种是Dictreader,先看reader:
import csv
with open('z.csv', 'r+', newline='',encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
for row in reader:
print(str(row))
运行结果:
['name', 'city', 'price']
['清华珠三角研究院', '广州-黄埔区', '1-1.5万/月']
['上海桥之队教育科技有限公司', '上海-杨浦区', '6-8千/月']
['文思海辉技术有限公司Pactera Tec...', '成都', '1-1.5万/月']
['上海皓维电子股份有限公司', '上海-嘉定区', '1-2万/月']
with的用法稍后讨论,上面运行结果,那么我们深入到reader这个方法本身去看看究竟,python源代码如下:
def reader(iterable, dialect='excel', *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
"""
csv_reader = reader(iterable [, dialect='excel']
[optional keyword args])
for row in csv_reader:
process(row)
The "iterable" argument can be any object that returns a line
of input for each iteration, such as a file object or a list. The
optional "dialect" parameter is discussed below. The function
also accepts optional keyword arguments which override settings
provided by the dialect.
The returned object is an iterator. Each iteration returns a row
of the CSV file (which can span multiple input lines).
"""
pass
这是python官方对于此方法的解释,用法全部都说了。简单明了,所以养成看官方文档的习惯很重要,reader函数,接收一个可迭代的对象,可以是一个列表,也可以是一个文件句柄,返回的便是每一行的数据。我试过了,如果你输入的是一个列表或者字符串,他会把其中的元素都进行迭代出来。iterable方法是衡量一个对象是否是可迭代对象,但是可迭代对象都是迭代器吗?No,要想一个对象是可迭代对象,首先要实现其next方法,这样的对象才会被称为可迭代对象。
再来看下一种方式,DictReader方法,首先看此方法的实际运用:
import csv
for d in csv.DictReader(open('z.csv', 'r+', newline='',encoding='utf-8')):
print (d)
运行结果:
OrderedDict([('name', '清华珠三角研究院'), ('city', '广州-黄埔区'), ('price', '1-1.5万/月')])
OrderedDict([('name', '上海桥之队教育科技有限公司'), ('city', '上海-杨浦区'), ('price', '6-8千/月')])
OrderedDict([('name', '文思海辉技术有限公司Pactera Tec...'), ('city', '成都'), ('price', '1-1.5万/月')])
OrderedDict([('name', '上海皓维电子股份有限公司'), ('city', '上海-嘉定区'), ('price', '1-2万/月')])
OrderedDict为python中的有序字典,他会记录字段的顺序,按顺序输出。
再来分析python关于此类的源码:
class DictReader:
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
dialect="excel", *args, **kwds):
self._fieldnames = fieldnames # list of keys for the dict
self.restkey = restkey # key to catch long rows
self.restval = restval # default value for short rows
self.reader = reader(f, dialect, *args, **kwds)
self.dialect = dialect
self.line_num = 0
def __iter__(self):
return self
@property
def fieldnames(self):
if self._fieldnames is None:
try:
self._fieldnames = next(self.reader)
except StopIteration:
pass
self.line_num = self.reader.line_num
return self._fieldnames
@fieldnames.setter
def fieldnames(self, value):
self._fieldnames = value
def __next__(self):
if self.line_num == 0:
# Used only for its side effect.
self.fieldnames
row = next(self.reader)
self.line_num = self.reader.line_num
# unlike the basic reader, we prefer not to return blanks,
# because we will typically wind up with a dict full of None
# values
while row == []:
row = next(self.reader)
d = OrderedDict(zip(self.fieldnames, row))
lf = len(self.fieldnames)
lr = len(row)
if lf < lr:
d[self.restkey] = row[lf:]
elif lf > lr:
for key in self.fieldnames[lr:]:
d[key] = self.restval
return d
这个类里面用到了python的高级用法,装饰器的嵌套应用,此部分内容移步到我的装饰器讲解篇,这样会更加的清晰,那么在这里要分析的是,这个类里面的next方法,实际上调用了_csv模块里的reader,把其中的每行的元素与字段,再次封装成一个有序字典来进行返回,这里可以看到我刚才说的,这两种读取方法都返回了可迭代对象,那么可迭代对象必然要实现next方法。
说完读方法,再来讨论一下CSV文件的写操作。那么在平常的应用之中,我们经常把爬取到的内容写入CSV,我们知道,从爬虫的大部分实现方法来说,都是生成了一个字典,那么我们写入CSV时,我们想简单一些,把爬取出来的字典直接写入,这样很方便,那么python显然已经为我们实现了这个方法,这就是DictWriter。先看用法:
file = open('zane.csv', 'w', newline='', encoding='utf-8')
headers = ['price', 'district', 'name', 'layout', "space", 'floor', 'RentType','loc']
writers = csv.DictWriter(file, headers)
writers.writeheader()
for item in a.Prase_info(url):
writers.writerow(item)
这段代码直接是用不了的,因为是我爬虫程序删减过后的,知道用法,自己写一个很简单,看看运行结果:
price,district,name,layout,space,floor,RentType,loc
5000,香洲区,华发新城二期,4室2厅,164平米,高层/11层,整租,"113.526232893,22.2376243653"
5800,香洲区,华发新城二期,4室2厅,165平米,中层/11层,整租,"113.526232893,22.2376243653"
3000,香洲区,嘉园,4室2厅,95.11平米,高层/6层,整租,"117.179603933,39.1072266636"
2800,香洲区,心海州 (心悦湾),2室2厅,60平米,高层/29层,整租,"113.530491251,22.2476647077"
5000,横琴新区,华融琴海湾,2室2厅,87平米,高层/19层,整租,"113.549125605,22.1416825418"
4300,香洲区,华发新城五期,2室2厅,90平米,中层/30层,整租,"113.516777146,22.2377163421"
上述结果是爬虫结果的一部分,可以看出已经成功写入,下面结合python源代码进行分析:
class DictWriter:
def __init__(self, f, fieldnames, restval="", extrasaction="raise",
dialect="excel", *args, **kwds):
self.fieldnames = fieldnames # list of keys for the dict
self.restval = restval # for writing short dicts
if extrasaction.lower() not in ("raise", "ignore"):
raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'"
% extrasaction)
self.extrasaction = extrasaction
self.writer = writer(f, dialect, *args, **kwds)
def writeheader(self):
header = dict(zip(self.fieldnames, self.fieldnames))
self.writerow(header)
def _dict_to_list(self, rowdict):
if self.extrasaction == "raise":
wrong_fields = rowdict.keys() - self.fieldnames
if wrong_fields:
raise ValueError("dict contains fields not in fieldnames: "
+ ", ".join([repr(x) for x in wrong_fields]))
return (rowdict.get(key, self.restval) for key in self.fieldnames)
def writerow(self, rowdict):
return self.writer.writerow(self._dict_to_list(rowdict))
def writerows(self, rowdicts):
return self.writer.writerows(map(self._dict_to_list, rowdicts))
# Guard Sniffer's type checking against builds that exclude complex()
try:
complex
except NameError:
complex = float
从我们首先执行此句writers = csv.DictWriter(file, headers)时,可以看出在DictWriter类中,我们的headers也就是我们要写入的字段名,被传入到了fieldnames变量中,可以看此变量的注释,他说这是字典键的列表。结合用法,我们最后传入到这里的字典不就是以我们每个字段名为键的字典吗。再看,当我们执行writers.writeheader()时,代码里首先用zip函数,把列表转换成了元组,把元组在转换为字典,写入到了首行之中。接下来的方法,_dict_to_list就是把我们最后传入的字典转换为列表,根据键名去写值。这里还看到,如果我们传入的字典的键与之前写入的对不上,会抛出异常。最后我们只需要在我们需要迭代写入的地方,调用writerow方法,可以实现把字典写入我们的CSV文件之中。
二 普通文本文件及with
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
程序示例:
f = open(yourfilepath, 'r')
ss = f.read() # 读进文件的全部内容返回一个字符串
ss = f.read(1024) # 读取指定字节的内容返回一个字符串
line = f.readline() # 读取文件的一行返回字符串(包含换行符)
line = line.strip("\n") # 处理的时候一般要去掉换行符(这里是\n)
lines = f.readlines() # 读取所有行返回一个列表list,每个元素(类型为字符串)为文件的一行
for line in f.readlines():
pass
f.close() # 文件用完要记得关闭,可以用with关键字,不用手动关闭,程序会自动关闭
# 以下均用with来读写文件
with open('yourfilepath', 'w') as tmpf:
a = 100; b = 97.5; c = 'Good'
tmpf.write('number=%d score=%f result%s' % (a, b, c))
# 或者直接写入文件内容——字符串(或二进制数据)
ss = 'yourstring'
f.write(ss) # 直接写入
ss = 'yourstring'
f.writeline(ss) # 写入时会自动加入换行符
ss = ['a', 'b', 'c']
f.writelines(ss) # 参数为字符串序列
python中的open和操作系统的open返回值都是文件描述符,但是有的人会问,为什么用with。那么我们来分析一下,如果我们open一个文件之后,如果读写发生了异常,是不会调用close()的,那么这会造成文件描述符的资源浪费,久而久之,会造成系统的崩溃。那么我们怎么去解决呢,python里面为我们提供了一种解决方案,那就是with,也叫上下文管理器。那么with具体是怎么实现的呢。在迭代器的部分,我们知道所有可迭代对象都实现了iterable方法,迭代器又多实现了next方法,那么,在with中,我们必须实现两个方法才能进行上下文也就是with的作用,那就是__enter__方法和__exit__方法。看下面实例:
class File():
def __init__(self,filename,mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print("entering")
self.f = open(self.filename,self.mode)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
print("will exit")
with File('out.txt','w') as f:
f.write("hello")
运行结果如下:
C:\Users\Administrator\PycharmProjects\装饰器\venv\Scripts\python.exe C:/Users/Administrator/PycharmProjects/装饰器/with上下文管理器.py
entering
will exit
Process finished with exit code 0
可以看出,with ...as之后的语句,相当于调用了__enter__方法,在读取成功或者异常,它在调用__exit__方法。那么还有一种实现with的方法,那就是装饰器,这个装饰器是python提供的,请看实例:
from contextlib import contextmanager
@contextmanager
def my_open(path,mode):
f = open(path,mode)
yield f
f.close()
with my_open('out.txt','w') as f:
f.write("hello, the simplest context manager")
这里的用法这么理解最好,yield上面便是enter方法,下面便是exit方法,用生成器来实现上下文管理。
三 xml json读写
xml 如果用传统语言的话,最好用的库是libxml,python也支持了,这里只讲最简单的写法。
rom lxml import etree, objectify
E = objectify.ElementMaker(annotate=False)
anno_tree = E.annotation(
E.folder('VOC2014_instance'),
E.filename("test.jpg"),
E.source(
E.database('COCO'),
E.annotation('COCO'),
E.image('COCO'),
E.url("http://test.jpg")
),
E.size(
E.width(800),
E.height(600),
E.depth(3)
),
E.segmented(0),
)
etree.ElementTree(anno_tree).write("text.xml", pretty_print=True)
输出结果:
<annotation>
<folder>VOC2014_instance/person</folder>
<filename>test.jpg</filename>
<source>
<database>COCO</database>
<annotation>COCO</annotation>
<image>COCO</image>
<url>http://test.jpg</url>
</source>
<size>
<width>800</width>
<height>600</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>person</name>
<bndbox>
<xmin>100</xmin>
<ymin>200</ymin>
<xmax>300</xmax>
<ymax>400</ymax>
</bndbox>
<difficult>0</difficult>
</object>
</annotation>
读取xml文件,用xpath最简单,百度xpath用法就好了,后续关于爬虫的章节,会对xpath重点讲。
下面来说说json 爬虫的时候最幸福的事就是遇到json,不再用xpath解析了,可以像操作字典一样操作json例子如下:
{
"fontFamily": "微软雅黑",
"fontSize": 12,
"BaseSettings":{
"font":1,
"size":2
}
}
import json
def loadFont():
f = open("Settings.json", encoding='utf-8') //设置以utf-8解码模式读取文件,encoding参数必须设置,否则默认以gbk模式读取文件,当文件中包含中文时,会报错
setting = json.load(f)
family = setting['BaseSettings']['size'] //注意多重结构的读取语法
size = setting['fontSize']
return family
t = loadFont()
print(t)
结果:
很简单的。