scrapy爬虫框架
Posted ccmldl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scrapy爬虫框架相关的知识,希望对你有一定的参考价值。
这是我近期学习的一些内容,可能不仅仅局限于scrapy爬虫框架,还会有很多知识的扩展。写的可能不是那么有条理,想到什么就写什么吧,毕竟也是自己以后深入学习的基础,有些知识说的不够明白欢迎留言,共同学习!
一、框架详解
Scrapy是由Twisted写的一个受欢迎的python事件驱动网络框架,它使用的是非阻塞的异步处理。
1.内部各组件的作用
ScrapyEngine(scrapy引擎):是用来控制整个系统的数据处理流程,并进行事务处理的触发。
Scheduler(调度器):用来接收引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回。它就像是一个URL的优先队列,由它来决定下一个要抓取的网址是什么,同时在这里会去除重复的网址。
Downloader(下载器):用于下载网页内容,并将网页内容返回给爬虫(Spiders)(Scrapy下载器是建立在Twisted这个高效的异步模型上的)。
Spiders(爬虫):爬虫主要是干活的,用于从特定网页中提取自己需要的信息,即所谓的实体(Item)。也可以从中提取URL,让scrapy继续爬取下一个页面。
Pipeline(项目管道):负责处理爬虫从网页中爬取的实体,主要的功能就是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被送到项目管道,并经过几个特定的次序处理数据。
Downloader Middlewares(下载器中间件):位于scrapy引擎和下载器之间的框架,主要是处理scrapy引擎与下载器之间的请求及响应。设置代理ip和用户代理可以在这里设置。
Spider Middlewares(爬虫中间件):位于scrapy引擎和爬虫之间的框架,主要工作是处理爬虫的响应输入和请求输出。
Scheduler Middlewares(调度中间件):位于scrapy引擎和调度器之间的框架,主要是处理从scrapy引擎发送到调度器的请求和响应。
2.Scrapy运行流程
引擎从调度器中取出一个URL用于接下来的抓取
引擎把URL封装成一个请求(Request)传给下载器
下载器把资源下载下来,并封装成一个响应(Response)
爬虫解析Response
解析出的是实体(Item),则交给项目管道(Pipeline)进行进一步的处理
解析出的是链接(URL),则把URL交给调度器等待下一步的抓取
二、为什么使用scrapy?爬虫能做什么?
1.Scrapy vs requests+beautifulsoup
首先,requests和beautifulsoup都是库,scrapy是框架;在scrapy框架中可以加入requests和beautifulsoup,它是基于Twisted(异步非阻塞)实现的,性能上有很大的优势;scrapy方便扩展,提供了很多内置的功能;它内置的css和Xpath以及selector提取数据的时候非常高效,beautifulsoup最大的缺点就是慢。
2.爬虫能做什么?
搜索引擎(如百度、Google)、推荐引擎(如今日头条)、机器学习的数据样本、数据分析(如金融数据分析)、舆情分析等
三、爬虫基础知识
1.正则表达式
为什么使用正则表达式?
有时候,我们爬取一些网页具体内容时,会发现我们只需要这个网页某个标签的一部分内容,或者这个标签的某个属性的值时,用xpath和css不太好提取数据,这时候我们就需要用到正则表达式去匹配提取。
re模块简介
1 import re 2 r‘‘‘ 3 re.match函数 4 原型:match(pattern, string, flags=0) 5 参数: 6 pattern:匹配的正则表达式 7 string:要匹配的字符串 8 flags:标志位,用于控制正则表达式的匹配方式,值如下 9 re.I: 忽略大小写 10 re.L: 做本地化识别 11 re.M: 多行匹配,只影响^和$,对每一行进行操作 12 re.S: 使.匹配包括换行符在内的所有字符 13 re.U: 根据Unicode字符集解析字符,影响w W B 14 re.X: 使我们以更灵活的格式理解正则表达式 15 注意:多个选项同时使用时用"|"隔开,如:re.I|re.L|re.M|re.S|re.U|re.X 16 功能:尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配,成功的话返回None 17 ‘‘‘ 18 # www.baidu.com 19 print(re.match(‘www‘, ‘www.baidu.com‘)) 20 print(re.match(‘www‘, ‘www.baidu.com‘).span()) 21 print(re.match(‘www‘, ‘wwW.baidu.com‘, flags=re.I)) 22 # 返回None 23 print(re.match(‘www‘, ‘ww.baidu.com‘)) 24 print(re.match(‘www‘, ‘baidu.www.com‘)) 25 print(re.match(‘www‘, ‘wwW.baidu.com‘)) 26 # 扫描整个字符串返回从起始位置成功的匹配 27 print(‘----------------------------------------------------------‘) 28 r‘‘‘ 29 re.search函数 30 原型:search(pattern, string, flags=0) 31 参数: 32 pattern:匹配的正则表达式 33 string:要匹配的字符串 34 flags:标志位,用于控制正则表达式的匹配方式 35 功能:扫描整个字符串,并返回第一个成功的匹配 36 ‘‘‘ 37 print(re.search(‘sunck‘, ‘good man is sunck!sunck is nice‘)) 38 39 print(‘---------------------------------------------------------‘) 40 r‘‘‘ 41 re.findall函数 42 原型:findall(pattern, string, flags=0) 43 参数: 44 pattern:匹配的正则表达式 45 string:要匹配的字符串 46 flags:标志位,用于控制正则表达式的匹配方式 47 功能:扫描整个字符串,并返回结果列表 48 ‘‘‘ 49 print(re.findall(‘sunck‘, ‘good man is sunck!sunck is nice‘)) 50 print(re.findall(‘sunck‘, ‘good man is sunck!Sunck is nice‘, flags=re.I)) 51 52 print(‘---------------------------------------------------------‘)
常用的一些正则元字符
1 import re 2 print(‘------------------匹配单个字符与数字------------------‘) 3 r‘‘‘ 4 . 匹配除换行符以外的任意字符 5 * 匹配0个或多个重复的字符,只装饰前面的一个字符 贪婪模式 6 + 匹配1个或者多个重复的字符,只装饰前面的一个字符 贪婪模式 7 ? 匹配0个或者1个字符,只装饰前面的一个字符 8 [0123456789] []是字符集合,表示匹配方括号中所包含的任意一个字符 9 [sunck] 匹配‘s‘,‘u‘,‘n‘,‘c‘,‘k‘中的任意一个字符 10 [a-z] 匹配任意小写字母 11 [A-Z] 匹配任意大写字母 12 [0-9] 匹配任意数字,类似[0123456789] 13 [0-9a-zA-Z] 匹配任意的数字和字母 14 [0-9a-zA-Z_] 匹配任意的数字、字母和下划线 15 [^sunck] 匹配除了‘s‘,‘u‘,‘n‘,‘c‘,‘k‘这几个字母以外的所有字符,中括号的"^"称为脱字符,表示不匹配集合中的字符 16 [^0-9] 匹配所有的非数字字符 17 d 匹配所有的数字,效果同[0-9] 18 D 匹配所有的非数字字符,效果同[^0-9] 19 w 匹配数字、字母和下划线,效果同[0-9a-zA-Z_] 20 W 匹配非数字、字母和下划线,效果同[^0-9a-zA-Z_] 21 s 匹配所有的空白符(空格、换行、回车、换页、制表),效果同[ f (v)] 22 S 匹配任意的非空白符,效果同[^ f (v)] 23 ‘‘‘ 24 print(re.search(‘.‘, ‘sunck is a good man‘)) 25 print(re.search(‘[0123456789]‘, ‘sunck is a good 3 man‘)) 26 print(re.search(‘[0123456789]‘, ‘sunck is a good man 6‘)) 27 print(re.search(‘[sunck]‘, ‘sunck is a good 3 man‘)) 28 print(re.findall(‘[^d]‘, ‘sunck is a good 3 man‘)) 29 print(‘------------------锚字符(边界字符)------------------‘) 30 r‘‘‘ 31 ^ 行首匹配,和在[]里的^不是一个意思 32 $ 行尾匹配 33 A 匹配字符串开始,它和^的区别是:A只匹配整个字符串的开头,即使在re.M模式下也不会匹配它行的行首 34 匹配字符串结尾,它和$的区别是:只匹配整个字符串的结尾,即使在re.M模式下也不会匹配它行的行尾 35 匹配一个单词的边界,指单词结束的位置能匹配,r‘er‘可以匹配never,不能匹配nerve,也就是-w和-W相邻的时候是边界 不转义的是时候是删除上一个字符 36 B 匹配非单词的边界,指单词里面的位置能匹配,r‘erB‘可以匹配nerve,不能匹配never 37 ‘‘‘ 38 print(re.search(‘^sunck‘, ‘sunck is a good man‘)) 39 print(re.search(‘sunck$‘, ‘sunck is a good man‘)) 40 print(re.search(‘man$‘, ‘sunck is a good man‘)) 41 print(re.search(r‘er‘, ‘never‘)) 42 print(re.search(r‘er‘, ‘nerve‘)) 43 print(re.search(r‘erB‘, ‘never‘)) 44 print(re.search(r‘erB‘, ‘nerve‘)) 45 print(‘------------------匹配多个字符------------------‘) 46 r‘‘‘ 47 说明:下方的x、y、z均为假设的普通字符,n、m均为非负整数,不是正则表达式的元字符 48 (xyz) 匹配小括号内的xyz(作为一个整体去匹配) 49 x? 匹配0个或者1个x 50 x* 匹配0个或者任意多个x,(.* 表示匹配0个或者任意多个字符(换行符除外)) 51 x+ 匹配至少一个x 52 x{n} 匹配确定的n个x(n是一个非负整数) 53 x{n,} 匹配至少n个x 54 x{n,m} 匹配至少n个最多m个x,注意:n <= m 55 x|y 匹配的是x或y,|表示或 56 print(re.findall(r"a.*c|b[w]{2}$", ‘asacbas‘)) 57 注意:多个正则表达式相或的时候,匹配过程是从左向右依次尝试匹配,如果左边有一个或者若干个匹配已经把整个字符串匹配完了, 58 那么后面的表达式将不再进行匹配。匹配是从左向右依次使用正则表达式去匹配前面匹配后剩下来的字符串 59 60 ‘‘‘ 61 print(re.findall(r‘(sunck)‘, ‘sunckgood is a good man,sunck is a nice man‘)) 62 print(re.findall(r‘a?‘, ‘aaa‘)) # 非贪婪匹配(尽可能少的匹配) 63 print(re.findall(r‘a*‘, ‘aaabbaaa‘)) # 贪婪匹配(尽可能多的匹配) 64 print(re.findall(r‘a+‘, ‘aaabbaaaaaaaaaaaa‘)) # 贪婪匹配(尽可能多的匹配) 65 # 需求,提取sunck----man 66 str = ‘sunck is a good man!sunck is a nice man!sunck is a very handsome man‘ 67 print(re.findall(r‘sunck.*?man‘, str)) 68 69 print(‘------------------特殊------------------‘) 70 r‘‘‘ 71 *? +? x? 最小匹配 通常都是尽可能多的匹配,可以使用这种方式解决贪婪匹配 72 (?:x) 类似(xyz),但不表示一个组,取消组的缓存,无法通过group()获取其中的组 73 (?P<name>) 给一个组起个别名,group()的时候可以直接使用该名字进行匹配 74 (?P=name) 命名反向引用,可以直接使用前面命名的组 75 (a|b)1 匿名分组的反向引用,通过组号匹配(aa|bb) 76 (a|b)(c|d)12 (acac|adad|bcbc|bdbd) 77 abc(?=123) 前向断言,匹配前面紧跟着123的abc 78 abc(?!123) 前向断言,匹配前面不能是紧跟着123的abc 79 (?<=123)abc 后向断言,匹配后面紧跟着123的abc 80 (?<!123)abc 后向断言,匹配后面不能是紧跟着123的abc 81 ‘‘‘ 82 # 注释: /* part1 */ /* part2 */ 83 print(re.findall(r‘//*.*/*/‘, r‘/* part1 */ /* part2 */‘)) 84 print(re.findall(r‘//*.*?/*/‘, r‘/* part1 */ /* part2 */‘)) 85 # 命名反向引用 86 print(re.search(r‘d(?P<first>ab)+(?P=first)‘, ‘asagghhdababhjdkfdabjjk‘)) 87 # 匿名分组的反向引用,通过组号匹配 88 m = re.search(r‘d(a|b)1‘, ‘daa|dbb‘) 89 if m: 90 print(m.group()) 91 m = re.search(r‘d(a|b)(c|d)12‘, ‘dacac|dadad|dbcbc|dbdbd‘) 92 if m: 93 print(m.group()) 94 # 前向断言 95 m = re.search(r‘abc(?=123)‘, ‘abc123‘) 96 if m: 97 print(m.group()) 98 m = re.search(r‘abc(?!123)‘, ‘abc124‘) 99 if m: 100 print(m.group()) 101 # 后向断言 102 m = re.search(r‘(?<=123)abc‘, ‘123abc‘) 103 if m: 104 print(m.group()) 105 m = re.search(r‘(?<!123)abc‘, ‘123abc‘) 106 if m: 107 print(m.group()) 108 109 r‘‘‘ 110 . 通配符,代表一个任意字符 111 ‘‘‘ 112 # print(re.findall(r‘l..e‘, a)) 113 r‘‘‘ 114 * 0个或者多个重复的字符,只装饰前面的一个字符 贪婪模式 115 ‘‘‘ 116 # print(re.findall(r‘l.*e‘, a)) 117 r‘‘‘ 118 ^ 以......开头 119 ‘‘‘ 120 # print(re.findall(r‘^I‘, a)) 121 r‘‘‘ 122 $ 以......结尾 123 ‘‘‘ 124 # print(re.findall(r‘^a.*b$‘, ‘adfgfhb‘)) 125 r‘‘‘ 126 + 1个或者多个重复的字符,只装饰前面的一个字符 贪婪模式 127 ‘‘‘ 128 # print(re.findall(r‘^aa+b$‘, ‘aaaa2aab‘)) 129 r‘‘‘ 130 ? 0个或者1个字符,只装饰前面的一个字符 可以把贪婪模式转换为非贪婪模式 131 ‘‘‘ 132 # print(re.findall(r‘ab?‘, ‘aaaa2aab‘)) 133 r‘‘‘ 134 {m} m个字符,只装饰前面的一个字符 135 ‘‘‘ 136 # print(re.findall(r‘ab{5}‘, ‘aaabbbbba2aab‘)) 137 r‘‘‘ 138 {m,n} 至少m个最多n个字符,只装饰前面的一个字符 139 ‘‘‘ 140 # print(re.findall(r‘ab{3,5}‘, ‘aaabbbbba2aab‘)) 141 r‘‘‘ 142 {m,} 至少m个字符,只装饰前面的一个字符 143 ‘‘‘ 144 # print(re.findall(r‘ab{3,}‘, ‘aaabbbbba2aab‘)) 145 r‘‘‘ 146 [] 是一个字符集合,在这里面元字符就失去原来的作用,成为普通字符 147 [^abc] 除了集合中的元素,其他的元素都可以匹配到 148 [^^] 只要不是^都可以匹配到 149 - 表示区间,如果想表示-写到最前面或者最后面[a-],或者使用转义‘‘ 150 ‘‘‘ 151 # print(re.findall(r‘ab[a-z]‘, ‘aaabbbbba2aab‘)) 152 r‘‘‘ 153 d{9}$ :表示以d{9}结尾 154 d 匹配所有的数字,效果同[0-9] 155 D 匹配所有的非数字字符,效果同[^0-9] 156 w 匹配数字、字母和下划线,效果同[0-9a-zA-Z_] 157 W 匹配非数字、字母和下划线,效果同[^0-9a-zA-Z_] 158 s 匹配所有的空白符(空格、换行、回车、换页、制表),效果同[ f (v)] 159 S 匹配任意的非空白符,效果同[^ f (v)] 160 ‘‘‘ 161 # print(re.findall(r"a.*c|b[w]{2}$", ‘asacbas‘)) 162 # 注意:多个正则表达式相或的时候,匹配过程是从左向右依次尝试匹配,如果左边有一个或者若干个匹配已经把整个字符串匹配完了, 163 # 那么后面的表达式将不再进行匹配。匹配是从左向右依次使用正则表达式去匹配前面匹配后剩下来的字符串 164 # 匹配特殊字符可以使用‘‘或者‘[]‘,* + ? [] () {}等特殊字符就变成一个普通字符了 165 # ret = re.findall(r"d(ab)+", ‘asadacbdababadabs‘) 166 # print(ret) 167 # print(ret.group(1)) 168 # print(ret.group(2))
1 import re 2 r‘‘‘ 3 字符串切割 4 ‘‘‘ 5 str = ‘sunck is a good man‘ 6 print(re.split(r‘ +‘, str)) 7 8 r‘‘‘ 9 re.finditer函数 10 原型:finditer(pattern, string, flags=0) 11 参数: 12 pattern:匹配的正则表达式 13 string:要匹配的字符串 14 flags:标志位,用于控制正则表达式的匹配方式 15 功能:与findall类似,扫描整个字符串,返回的是一个迭代器 16 ‘‘‘ 17 str1 = ‘sunck is a good man!sunck is a nice man!sunck is a handsome man‘ 18 d = re.finditer(r‘(sunck)‘, str1) 19 while True: 20 try: 21 L = next(d) 22 print(L) 23 except StopIteration as e: 24 break 25 26 r‘‘‘ 27 sunck is a good man 28 字符串的替换和修改 29 sub(pattern, repl, string, count=0, flags=0) 30 subn(pattern, repl, string, count=0, flags=0 31 参数: 32 pattern: 正则表达式(规则) 33 repl: 指定的用来替换的字符串 34 string: 目标字符串 35 count: 最多替换次数 36 flags: 标志位,用于控制正则表达式的匹配方式 37 功能:在目标字符串中以正则表达式的规则匹配字符串,再把他们替换成指定的字符串。可以指定替换的次数,如果不指定,替换所有的匹配字符串 38 区别:前者返回一个被替换的字符串,后者返回一个元组,第一个元素是被替换的字符串,第二个元素是被替换的次数 39 ‘‘‘ 40 str2 = ‘sunck is a good good good man ‘ 41 print(re.sub(r‘(good)‘, ‘nice‘, str2)) 42 print(type(re.sub(r‘(good)‘, ‘nice‘, str2))) 43 print(re.subn(r‘(good)‘, ‘nice‘, str2)) 44 print(type(re.subn(r‘(good)‘, ‘nice‘, str2))) 45 46 r‘‘‘ 47 分组: 48 概念:除了简单的判断是否匹配之外,正则表达式还有提取子串的功能。用()表示的就是提取出来的分组 49 50 ‘‘‘ 51 str3 = ‘010-52347654‘ 52 m = re.match(r‘(?P<first>d{3})-(?P<last>d{8})‘, str3) 53 # 使用序号获取对应组的信息,group(0)一直代表的是原始字符串 54 print(m.group(0)) 55 print(m.group(1)) 56 print(m.group(‘first‘)) 57 print(m.group(‘last‘)) 58 print(m.group(2)) 59 # 查看匹配的各组的情况 60 print(m.groups()) 61 62 r‘‘‘ 63 编译:当我们使用正则表达式时,re模块会干两件事 64 1、编译正则表达式,如果正则表达式本身不合法,会报错 65 2、用编译后的正则表达式去匹配对象 66 compile(pattern, flags=0) 67 pattern:要编译的正则表达式 68 flags:标志位,用于控制正则表达式的匹配方式 69 ‘‘‘ 70 pat = r‘^1(([3578]d)|(47))d{8}$‘ 71 print(re.match(pat, ‘13600000000‘)) 72 # 编译成正则对象 73 re_telephone = re.compile(pat) 74 print(re_telephone.match(‘13600000000‘)) 75 76 # re模块调用 77 # re对象调用 78 79 # re.match(pattern, string, flags=0) 80 # re_telephone.match(string) 81 82 # re.search(pattern, string, flags=0) 83 # re_telephone.search(string) 84 85 # re.findall(pattern, string, flags=0) 86 # re_telephone.findall(string) 87 88 # re.finditer(pattern, string, flags=0) 89 # re_telephone.finditer(string) 90 91 # re.split(pattern, string, maxsplit=0, flags=0) 92 # re_telephone.split(string, maxsplit=0) 93 94 # re.sub(pattern, repl, string, count=0, flags=0) 95 # re_telephone.sub(repl, string, count=0) 96 97 # re.subn(pattern, repl, string, count=0, flags=0) 98 # re_telephone.subn(repl, string, count=0)
1 r‘‘‘ 2 javascript RegExp 对象 3 RegExp 对象: 4 正则表达式是描述字符模式的对象。正则表达式用于对字符串模式匹配及检索替换,是对字符串执行模式匹配的强大工具。 5 语法:var patt=new RegExp(pattern,modifiers);或者var patt=/pattern/modifiers; 6 参数: 7 pattern(模式) 描述了表达式的模式 8 modifiers(修饰符) 用于指定全局匹配、区分大小写的匹配和多行匹配 9 注意:当使用构造函数创造正则对象时,需要常规的字符转义规则(在前面加反斜杠 )。比如,以下是等价的: 10 var re = new RegExp("\\w+"); 11 var re = /w+/; 12 ‘‘‘ 13 14 r‘‘‘ 15 修饰符:修饰符用于执行区分大小写和全局匹配 16 i 执行对大小写不敏感的匹配。 17 g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 18 m 执行多行匹配。 19 ‘‘‘ 20 21 r‘‘‘ 22 方括号:方括号用于查找某个范围内的字符 23 [abc] 查找方括号之间的任何字符。 24 [^abc] 查找任何不在方括号之间的字符。 25 [0-9] 查找任何从 0 至 9 的数字。 26 [a-z] 查找任何从小写 a 到小写 z 的字符。 27 [A-Z] 查找任何从大写 A 到大写 Z 的字符。 28 [A-z] 查找任何从大写 A 到小写 z 的字符。 29 [adgk] 查找给定集合内的任何字符。 30 [^adgk] 查找给定集合外的任何字符。 31 (red|blue|green) 查找任何指定的选项。 32 ‘‘‘ 33 34 r‘‘‘ 35 元字符:元字符(Metacharacter)是拥有特殊含义的字符 36 . 查找单个字符,除了换行和行结束符。 37 w 查找单词字符。 38 W 查找非单词字符。 39 d 查找数字。 40 D 查找非数字字符。 41 s 查找空白字符。 42 S 查找非空白字符。 43 匹配单词边界。 44 B 匹配非单词边界。 45