爬虫的五个步骤
- 明确需求,想想爬什么数据
- 确定含有需要数据的网站
- 分析请求类别,请求时所携带的参数,模拟发送请求
- 下载页面,分析页面,通过re,xpath来过滤response中返回的数据
- 将数据储存起来
正则表达式
正则表达式的定义
描述了一种字符串的匹配模式,可以用来检查一个串是否含有某种字串,见匹配到的字串替换成其他的字符或者取出
应用场景
测试字符串的是否符合某个模式
批量替换文本中符合某个模式的字符
正则表达式的使用
re.match 尝试从字符串的起始位置匹配
re.search 扫描整个字符串,匹配一次
re.findall 扫描整个字符串,匹配出所有符合模式的子串,返回列表
re.finditer 扫描方法是和findall相似,只是返回的结果是一个生成器
re.compile 在匹配模式不变,但是被匹配对象变化的时候使用
# compile 的使用
compile(pattern,flags=0)
pet = r'^1(([3597]\d)|(47))\d{8}$'
print(re.match(pet,'13600000000'))
re_telephone = re.compile(pet)
print(re_telephone.match('13600000000'))
re.sub:
re.sub(pattern, repl, string, count=0, flags=0) 匹配目标串,将目标串中被匹配到的子串替换成指定的字符串,可以指定替换次数,不指定将替换所有
re.subn:
re.subn(pattern, repl, string, count=0, flags=0):
和re.sub一样,前者返回的是被替换的字符串,后者返回一个元组,第一个元素表示被替换的字符串,第二个元素表示被替换的次数
匹配模式
模式 | 描述 |
---|---|
^ | 匹配字符串的开头 |
$ | 匹配字符串的末尾。 |
. | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
[…] | 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’ |
[^…] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 |
re* | 匹配0个或多个的表达式。 |
re+ | 匹配1个或多个的表达式。 |
re? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
re{ n} | 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 “Bob” 中的 “o”,但是能匹配 “food” 中的两个 o。 |
re{ n,} | 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。“o{1,}” 等价于 “o+”。“o{0,}” 则等价于 “o*”。 |
re{ n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
a| b | 匹配a或b |
(re) | 匹配括号内的表达式,也表示一个组 |
(?imx) | 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。 |
(?-imx) | 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。 |
(?: re) | 类似 (…), 但是不表示一个组 |
(?imx: re) | 在括号中使用i, m, 或 x 可选标志 |
(?-imx: re) | 在括号中不使用i, m, 或 x 可选标志 |
(?#…) | 注释. |
(?= re) | 前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。 |
(?! re) | 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功 |
(?> re) | 匹配的独立模式,省去回溯。 |
\w | 匹配字母数字及下划线 |
\W | 匹配非字母数字及下划线 |
\s | 匹配任意空白字符,等价于 [\t\n\r\f]. |
\S | 匹配任意非空字符 |
\d | 匹配任意数字,等价于 [0-9]. |
\D | 匹配任意非数字 |
\A | 匹配字符串开始 |
\Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 |
\z | 匹配字符串结束 |
\G | 匹配最后匹配完成的位置。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 |
\B | 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 |
\n, \t, 等. | 匹配一个换行符。匹配一个制表符。等 |
\1…\9 | 匹配第n个分组的内容。 |
\10 | 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。 |
正则匹配列表
# 校检数字
数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|1-9*)$
非零开头的最多带两位小数的数字:^(1-9*)+(.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$
正数、负数、和小数:^(-|+)?\d+(.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d$ 或 ^(1-9){1,3} 或 ^\+?[1-9][0-9]*
非零的负整数:^-1-90-9"$ 或 ^-[1-9]\d$
非负整数:^\d+ 或 ^[1-9]\d*|0
非正整数:^-[1-9]\d*|0 或 ^((-\d+)|(0+))
非负浮点数:^\d+(.\d+)? 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0
非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?)) 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0
正浮点数:^[1-9]\d.\d|0.\d[1-9]\d 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))
负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d) 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))
浮点数:^(-?\d+)(.\d+)? 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)
# 校检字符
汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符:[^~\x22]+
# 特殊需求的匹配
Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
身份证号(15位、18位数字):^\d{15}|\d{18}$
短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
钱的输入格式:
有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$ <br>
备注:这就是最终结果了,别忘了"+"可以用"\*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
中文字符的正则表达式:[\u4e00-\u9fa5]
双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))
进程,线程,协程
进程
一个任务就是一个进程,拥有自己独立的堆栈段,代码段,数据段以及状态的保存,进程是系统中程序执行和资源分配的基本单位
进程在计算机系统中是按照固定的时常轮播调度执行的
进程和进程之间通过内核,网络,或者文件的方式来相互通信
线程
一个进程任务的完成需要一个或者多个子任务组合成完成,所以一个进程至少有一个线程,子任务就是这个进程的线程
线程共享进程的数据,多线程变相的延长了这个进程在计算机系统中所执行的时间
进程和线程的区别
进程是计算机系统进行资源分配和调度的独立单位,是数据层面,然而去实现进程中任务的是线程,线程是进程的实体,是进程的生命所在,是CPU调度和分配的基本单位,线程本身没有资源或者只拥有很少的一部分资源(线程计数器,寄存器,栈),但是它与其他线程共享所属进程的所有资源
一个程序至少需要一个进程,而一个进程至少需要用有一个线程
线程划分尺度小于进程,所以多线程的程序并发性高
进程执行的时候拥有独立的寄存单元,多线程共享进程的内存,提高了程序的运行效率
线程没有只能共享进程的资源,所以线程不能单独存在
优缺点:
- 多线程执行效率高,但是高并发不利于数据的管理和保护
- 进程中如果只有一个线程,实现便于数的保护,但是效率低
协程
微线程,比线程更小,是用户态的轻量级的线程
协程 拥有自己的寄存器,上下文和栈,完全由用户控制调度,调度时会将自己的状态保存到寄存器,当重新切换回来的时候,还可以继续这调度之前
的状态执行
协程的优缺点:
- 无需线程上下文切换的开销,避免了无意义 的调度,提高性能,但是程序员必须自己承担调度任务,而且失去了标准线程使用多CPU的能力
- 无需原子操作,同步锁,减低开销
- 无需切换控制流程,简化编程
- 高并发,高扩展,低成本 一个CPU支持上万的协程不是问题,很适合用于高并发的处理
- 缺点:
- 无法利用多核,协程还是在单线程的层面上运行,所以想利用多核,还需要和线程配合
- 发生阻塞操作的时候会阻塞整个程序
scrapy框架
scrpay中,数据流的走向:
- spider中 的start_url或者start_requests形成初始的request对象发送给Engine
- Engine判断出是request对象,将对象发送给Schedue
- Engine将从Schedule中获取request对象
- 获取到的request对象经过Download Middle Wares发送给Downloader
- Downloader从网络上获取数据后会生成response,这个response会再次经过Downloader Middle Ware 将response发送个Engine
- Engine获取到response之后,将response经过Spider Middle Ware 发送给Spiders,Spiders会处理response中的数据
- Spiders将处理之后的结果经过spider Middle Ware 发送给Engine
- Engine接收到Spider发送的结果之后进行判断:
- 如果结果是Item,那么发送个Item Pipeline处理这个Item
- 如果结果是request,那么发送给Schedule,重新进入请求循环
所涉及到的模块的作用
Scrapy Engine
负责整个数据流的控制以及事件触发的作用
Schedule
存储request
Downloader
负责request请求网络后的下载工作
Spiders
负责数据清洗,用户验证,以及数据储存的业务逻辑****
Download Middle Ware
处理发送到Downloader的request以及从Downloader返回的response
应用场景:
- 下载器下载前,必须添加一些信息
- 一个request想要生成另一个request
- 需要删除一部分拥有某个特征的request的时候
Spiders Middle Ware
介于Engine 和 Spiders之间
处理 Request,Response以及Item
应用场景:
- 处理输出的Item或者request
- 隐藏输入的response的错误
- 继续处理start_request中的内容
- 自定义去重算法
分布式爬虫
将scrapy中的Schedule更换成为Redis数据库,实现将Spider输出的request请求共享
共享的request请求就可以在多台机器上处理,这就实现了分布式
优点:
- 充分利用多台机器的带宽
- 充分利用多台机器的IP