Python学习

Python学习
官方文档
Python3中文手册

虚拟环境

Pipenv

一般和其他语言是没什么太大的不同, 这里标一些不一样的地方, 方便速查, 以防搞混

定义类的格式

# 注释格式

class Dog(父类):

 # 这里是类变量的声明和初始化, 类属性被每个实例共享
live = 10
# 这里传入的第一个参数其实是传入实例本身, 但是实例调用时候并不关心这个, 即: Dog.addtrick(x, 1) = x.addtrick(1)  // x 是一个Dog实例
 def __init__(self, name):
     self.name = name
     self.tricks = []

 def addTrick(self, trick):
     self.tricks.append(trick)

这里的作用域是靠缩进来区分的, 所以要比较注意这些缩进…

有多继承…

没有私有变量, 以下划线开头的变量, 方法会处理为Api的非公开部分

它结构体是这么定义的…很不明白..

1
2
3
4
5
6
7
8
9
class Employee:
pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

错误和异常处理栗子

1
2
3
4
5
6
7
8
9
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")

当try的东西出错时, 就会到except检查异常类型, 符合条件就拦截下来, 没出错就到else, else中的代码写到try的话, 就会妨碍需要保证正确的范围, 而finally的话是无论正确与否都会执行的.

在安装其他Python环境的时候, 碰上一些问题,

安装参考
安装参考2
安装参考3
安装科学计算环境
参考

之前我使用brew安装了Python3的, 在安装virtualenv时,

正则表达式

就像一个Filter, 给定一个String, 符合给定的规则的话就返回它, 不然就忽略.
一般正则字符串是一些咧线性规则构成的字符串: “a至少出戏一次 -> 后面出现 b 5次 -> 后面跟着任意出现偶数次出现的 c “ 这么一种线性规则
相对的非正则字符串的话就是一些含有一些非线性规律的字符串, 在爬虫中一般不会用到

正则表达式就是表达那样的线性规则的缩写

1
aa*bbbbb(cc)*(d | )

a后跟着的a, 表示”a重复任意次, 包括0次”, 这样就保证a至少出现1次
(cc)是编组, 后面跟着
表示就是这个组出现任意次, 这样就保证了偶数次出现的c,
(d | )表示最后一个的规则, “|”表示或逻辑, 栗子表示增加一个后面跟着空格的d, 或只有一个空格

自我总结:
正则分为每段的规则
“()” : 括号表示里面的东西做一个整体包起来了, / 提取子串, 表示将符合括号内规则的字符串提取出来, 有满足的话就会被提取出来
“: 表示任意个, 类似于乘, 重复的意思
“|”: 或逻辑
“[]”: 其中一个规则的集合, 表示这一个[]里的规则符合一个就行, 也可以是一个区间, [1-9], [.**], 这里面是字符匹配, 所以”.
“在里面就没有了所含义, 就是字面上的字符
“-“: 范围, 如: A-Z, a-z…
“+”: 可出现多次, 至少一次
“{m,n}”: 匹配前面的字符, 子表达式或括号里的字符m到n次, 包括m,n.
“[^]”: 匹配任意一个不在[]中的字符, 相当于取反
“.”: 任意字符
“^”: 值字符串开始位置的字符或子表达式
“$”: 从字符串的末端匹配, 常出现在表达式的末尾
“?”: 一般使用正则的时候会进行贪婪匹配, 也是从后往前进行规则的匹配. 如”booobboobba” 要匹配b.*b 的话, 会只有bb得出, 不能得出booob, 加上问号后会要求问号后进行从前往后第一个匹配, 同时我们在最后一个字符规则也加问号, 表明湖面这个字符也进行匹配第一个
“{}”: 也是一个限定, 限定前面的字符出现几次, 形式如: {1}, {1, }, {1,5}
“\s”: space, 表示空格
“\S”: 非空格
“\w”: 相当于[A-Za-z0-9_]
“\W”: 与\w相反
“\u4E00-\u9FA5”: 汉字的unicode编码, 匹配汉字
“\d”: 匹配数字类型

如果不想写完程序再验证正则表达式是否正确, 可以去”http://regexpal.com/" 这类的网站上去测试正确性

scrapy:

参考资料
中文翻译

Have a Glance

按照官方文档我们先建立一个 class, 继承自 scrapy.Spider, 在其框架下, 当然是充分利用它提供的东西啊~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import scrapy

class QuotesSpider(scrapy.Spider):
name = "quotes" # 这个是对爬虫的命名, 'name'是自带就有的 prop
# 这里
start_urls = [
'http://quotes.toscrape.com/tag/humor/',
]

def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').extract_first(),
'author': quote.xpath('span/small/text()').extract_first(),
}

next_page = response.css('li.next a::attr("href")').extract_first()
if next_page is not None:
yield response.follow(next_page, self.parse)

保存到文件里, 名字自定, 如: quotes_spider.py, 然后在文件所在的文件夹运行 scrapy的命令 scrapy runspider quotes_spider.py

发生了什么?

What just happened?
When you ran the command scrapy runspider quotes_spider.py, Scrapy looked for a Spider definition inside it and ran it through its crawler engine.
当你有耐心这个命令是, Scrapy 会去文件中找你定义的 Spider, 然后用它的 crawler 引擎来跑这个 spider
然后对定义在start_urls 中的链接发起请求, 然后 调用默认的 callback: parse, 用来解析这个 request’s response, 里面用 CSS 选择器来遍历所有的 quote, yield(异步抛出结果)一个包含了句子和作者的 Python 字典, 提起下一页的链接, 然后调度另一个请求并继续用这个 parse 函数来解析response, 直到yield 出的 request 结束.
注意到没, 这里体现 Scrapy 中之一: 请求是统一调度的且异步解析 response. 这意味着 Scrapy 不需要等待一个请求返回/解析完毕, 就可以发起另一个请求/解析的任务或者做其他什么事. 这也以为着其他请求在某个请求失败时 handle 某个 error时, 还可以继续任务, 不受影响.

使用scrapy的命令

在终端中:

  • 创建项目: scrapy startproject myproject [project_dir]
    进入到项目目录中
  • 创建新spider文件: scrapy genspider mydomain mydomain.com

Scrapy 提供了两种类型的命令。一种必须在 Scrapy 项目中运行(针对项目(Project-specific)的命令),另外一种则不需要(全局命令)。全局命令在项目中运行时的表现可能会与在非项目中运行有些许差别(因为可能会使用项目的设定)。

全局命令:

  • startproject
  • genspider
  • settings
  • runspider
  • shell
  • fetch
  • view
  • version
    项目(Project-only)命令:
  • crawl
  • check
  • list
  • edit
  • parse
  • bench

运行爬虫

  • $ scrapy crawl myspider

scrapy中默认是通过递归实现深度优先算法, 来爬去网站的URL
广度优先则是通过队列来实现

步奏

新建虚拟环境

技巧: 在pip安装时可以使用豆瓣源来加快安装过程, pip3 install -i https://pypi.douban.com/simple xxx

  1. 使用Python3 来创建一个virtualenv : 进入到项目目录后 -> virtualenv 虚拟环境名 -开始使用虚拟环境> source venv/bin/activate -> 像平常一样安装使用, 不需要使用这个虚拟环境的时候 -> deactivate, 具体适应可以参考使用参考
    第一步可以不实用virtualenv, 直接使用virtualenvwrapper来管理, 参考文章同上
    创建虚拟环境: mkvirtualenv venv -> 在虚拟环境中工作: workon venv ->
    (在安装时, 到这步: source /usr/local/bin/virtualenvwrapper.sh 可能会碰到bug)
    使用教程安装的话, 我现在用的是Python3安装的, 其安装在3的目录下,但是他的source路径还是在Python2.7 , 只要export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3 , 将其指向Python3即可. 继续source /usr/local/bin/virtualenvwrapper.sh 成功
  2. 进入到虚拟环境文件夹中安装scrapy
  3. 启动虚拟环境
  4. 使用scrapy新建项目 官方文档
  5. 进入项目中新建spider
  6. 在项目根目录创建一个文件, 来调用命令行来Run这个scrapy项目 – 文件中要设置工程的目录, 设置执行命令
  7. 对robot那个什么东西取消注释, 不然会根据网站的那个文件进行url过滤, 很多URL就没了, robottxt = FALSE
  8. 运行, 在断点处可以看到需要的信息了
  9. response中的body即使返回的整个html文本, response中还有其他的一些属性
    10.

xpath解析

在scrapy中, 使用xpath是比较普遍的, 而且也是官方支持的, 像其他支持很多CSS selector的库其实我知道的一些是转成xpath, 在进行查找的
所以直接使用xpath应该会快一点.
w3school教程
nodename: 选择nodename节点下的所有节点, nodename下的节点全部都要
/: 从根节点选取, 注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
//: 选择当前所在节点中的某个节点, 不管他们是嵌套的还是怎么样的, 只要是这个节点都可以, bb//xx: 选择bb下的所有cc, 不管是在bb下的任何地方. //xx, 选取所有xx, 不管在文档中的什么地方
.: 选取当前节点
..: 选取当前节点的父节点
@: 选取属性

谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。

有时候我们用到的class里不止包含一个字段, 这时如果用=xxx, 就会找不到, 需要使用包含xxx才行, 有两个都要指定时, 可以”css(.nes.jjj)”

| 路径表达式 | 结果 |
| /bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。|
| /bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。|
| /bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。|
| /bookstore/book[position()<3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。|
| //title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。|
| //title[@lang=’eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。|
| /bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。|
| /bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。|

  • response 自带有xpath和CSS选择器, 直接调用.xpath(‘xpath表达式’)就可以进行解析.
  • 返回的是一个selectorList, 这样就能在继续使用.xpath语法
  • .extract()[0]提取出其中的字符串, 如果里面有/r/n不想要的话, 可以用.strip(), 想替换其中的字符串的话可以用replace(“被替换的字符串”, “替换的字符串”)
  • 注意使用strip()只能去掉首尾的/r/n还有空格
  • 在限定属性的时候, 可以使用[@class = ‘’]. 有时候只想在属性中包含某些的时候, 可以使用一个韩式contain : //span[contains(@class, “xxxx”)]

通过解析获得的数据进行处理

  • 使用yield可以一个一个将对象返回, 然后调用指定的函数去处理response.
  • 可以拿到一个url就递归返回自身, 然后提取数据, 返回给处理具体事情的函数,
  • 处理具体事情时, 可以是提取信息的过程
  • 使用item来声明一个结构, 最后返回的东西必须是一个可迭代的东西, yield这个可以看做是一个generator, 返回的url或者item满足要求
  • 返回item后会进入到pipeline进行处理
  • 要让这个生效的话需要在setting中将ITEM_PIPELINE那段取消注释
  • pipeline主要用来将item放到数据库或者进行其他处理.
  • scrapy提供了自动下载图片的机制 它是用一个已经做好的pipeline来处理下载图片的功能, 我们只需要配置好就行了. 需要在setting中将pipeline添加到列表中: ‘scrapy.pipelines.images.ImagePipeline’: 1, pipeline后面跟的数字是东西进入pipeline里的顺序, 越小的越早进
  • 当item进入imagePipeline时, 会自动下载对应的图片链接, 我们需要在setting里声明在item中是哪个: IMAGES_URLS_FIELD = “图片链接的key”
  • 当然还能设置图片的保存位置: IMAGES_STORE = “保存的路径/ 可以是相对路劲个, 使用os.path.join(os.path.abspath(os.path.dirname(file)), ‘文件夹名’)”
  • 要保存图片还需要一个库: PIL, 不然运行会报错: pip3 install pillow
  • 在setting中设置的字段默认将值当做数组来处理, 所以需要将自动保存图片的key的value使用[]数组来赋值
  • 过滤图片: IMAGE_MINt_HEIGHT = 100; 同理设置做小宽度: IMAGE_MINT_WIDTH = 100, 这些限制的参数可以字啊ImagePipeline中设置
  • 其自动保存的图片会自动根据URL生成指纹, 来作为文件名
  • 要像对保存图片时做更多的自定义操作, 可以继承这个imagepipeline,
    -

Item

使用item这个类的实例来保存你和结构化解析的数据, 当函数将itemyield时, scrapy会自动将item传到pipeline中, 进行后续更详细的处理

response传值

在我们拿到一个URL后, 发起request请求, 同时想在request的response中拿到之前处理好的一些值的时候, 可以使用meta这个属性来传递

1
2
Request(meta={'font': 'asfasfa'})
在response中: font = response.meta['font'] or respose.meta.get('font', '默认值')

取字典可以使用get(), 设置默认值以防发生错误

在spider函数中导入这个item

可以将URL做hash来保存, 可以节省空间, 但是不必要
方法: 导入hashlib -> hashlib.md5.update(url).hexdigest()

抓去的数据进行持久化

可以用json格式进行持久化

import json, json.dorm….
需要打开一个文件, 设置文件的格式和读写权限等等

将数据保存到数据库

环境需要装mysql的驱动: pip3 install -i https://pypi.douban.com/simple/ mysqlclient
出现问题: Command “python setup.py egg_info” failed with error code 1 in /private/var/folders/qp/zd_ttf394yxbkkb1s9tqvgth0000gn/T/pip-build-wjt0tzud/mysqlclient/
安转失败

创建表
添加字段

######我们项目用的是PostgresSQL
安装PostgresSQL: 使用brew安装brew install postgresql(项目里使用服务器的pg, 所以在本地暂时没装), 还需要安装一个操作库: pip3 install psycopg2, 在Python中就能操作链接
安装redis: brew install redis, Python调用redis需要安装一个操作库, 这样在Python里才能识别: pip3 install redis, brew安装redis也可以装上

开启 redis:

1
redis-server /usr/local/etc/redis.conf

启动 postgrespl

1
pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start

查看当前数据库

1
psql -l

为数据库创建一个用户及密码

1
createuser rent_house -P

创建一个数据库

1
createdb rent_house -O rent_house -E UTF8 -e

然后就能够通过 pgcons2来链接数据库进行操作了

操作

首先需要在数据库中有一张表,
然后需要在将数据保存到数据库的pipeline的初始化中初始化数据库的链接, 设置号cursor: self.conn, self.corsor

异步操作数据库

有时候多线程抓去数据多的话, 会造成写入数据库速度跟不上解析的速度, 数据堆积.
scrapy有可以让他进行异步的操作 – 连接池

scrapy的调试方法

安装ipython之后, 可以在命令行里使用scrapy shell [网址], 就能拿到一个response, 进行css或xpath的解析调试

redis的操作命令

python 中使用redis
官方
参考

将项目通过scrapyd部署到服务器上

通过scrapyd, 我们可以通过部署在服务器上的web api 来进行爬虫的部署/ 启动/ 停止/ 并且查看运行日志
参考文档
参考资料
使用上的参考资料

简单步奏概要(运行在本机测试时):

  • 安装scrapyd(这个主要是在需要运行爬虫端的服务器安装): pip3 install scrapyd
  • 运行服务: scrapyd (默认情况是:6800端口)
  • 安装部署工具(这个是在链接到的客户端安装, 用来将本地编辑的爬虫部署到服务器上): pip3 install scrapyd-client
  • 修改工程目录下的 scrapy.cfg 文件:
1
2
3
4
[deploy:scrapyd2]   #默认情况下并没有scrapyd2,它只是一个名字,可以在配置文件中写多个名字不同的deploy
url = http://scrapyd.mydomain.com/api/scrapyd/ #要部署项目的服务器的地址
username = john #访问服务器所需的用户名和密码(如果不需要密码可以不写)
password = secret

warning: 在.cfg文件中, 格式要严格, 即使一个空格也不能多出

使用scrapyd-client来将project部署到scarapyd上

开启scrapyd服务后, 进入到project的根目录(有scrapy.cfg文件同个地方) 运行scrapyd-deploy命令, 剩下的看文档就行.

warning: 不要相信github上的readme…..scrapy-client命令根本就没有…直接用scrapyd-deploy来做部署就行, 仅做部署作用

在scrapyd上运行爬虫

参照文档, 调用Api传参就行

Python任务调度模块

参考资料
APScheduler是一个Python定时任务框架

在scrapy shell 中给请求添加header

there is no current way to add headers directly on cli, but you could do something like:

$ scrapy shell

from scrapy import Request
req = scrapy.Request(‘https://www.douban.com/group/topic/107785547', headers={‘User-Agent’: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4’})
fetch(req)

In [1]: req = scrapy.Request(‘https://www.douban.com/group/pudongzufang/discussi
…: on?start=0’, headers={‘User-Agent’: ‘Mozilla/5.0 (Windows NT 6.1; Win64;
…: x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari
…: /537.36’})

In [2]: req
Out[2]: <GET https://www.douban.com/group/pudongzufang/discussion?start=0>

In [3]: fetch(req)

re.match(‘.?(徐家汇|湖南路|上海图书馆|武康路|乌鲁木齐|兴国路|幸福路|平武路|镇宁路|番禺路|愚园路|江苏路|地铁+).‘, “华南医院有个湖南的人在湖南路”)

generator

遍历 Generator 的话直接用 for 就可以

for loop

1
2
for item in sequence:
expressions

sequence 是一个可迭代对象

1
2
for i in range(start, stop, step):
xxxx

stop 不包括在内

迭代器

Python 中的 for 句法实际上实现了设计模式中的迭代器模式 ,所以我们自己也可以按照迭代器的要求自己生成迭代器对象,以便在 for 语句中使用。 只要类中实现了 iter 和 next 函数,那么对象就可以在 for 语句中使用。 现在创建 Fibonacci 迭代器对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# define a Fib class
class Fib(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1

def __iter__(self):
return self

def __next__(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()

# using Fib object
for i in Fib(5):
print(i)

生成器

除了使用迭代器以外,Python 使用 yield 关键字也能实现类似迭代的效果,yield 语句每次 执行时,立即返回结果给上层调用者,而当前的状态仍然保留,以便迭代器下一次循环调用。这样做的 好处是在于节约硬件资源,在需要的时候才会执行,并且每次只执行一次。

1
2
3
4
5
6
7
8
9
10
11
def fib(max):
a, b = 0, 1
while max:
r = b
a, b = b, a+b
max -= 1
yield r

# using generator
for i in fib(5):
print(i)