数据提取方法
一、基础知识
数据提取
从响应中获取我们想要的数据的过程
数据分类
结构化数据
类型:json、xml等
处理方法:转化为python数据类型
非机构化数据
类型:html等
处理方法:正则表达式、xpath
二、Json知识点
JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它使得人们很容易的进行阅读和编写,
同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互
JSON数据提取
使用Chrome切换到手机页面
抓包手机app的软件
Json与python数据转换
loads和dumps对字符串处理
json————>python json.loads()
import pprint import pprint 可以起美化作用
python————>json json.dumps()
load和dump对包含json的类文件对象处理
具有read()或者write()方法的对象就是类文件对象
f = open('a.txt','r') f就是类文件对象
json————>python json.load()
python————>json json.dump()
Json使用注意点
json中的字符串都是双引号引起来的
如果不是双引号
eval:能实现简单的字符串和python类型的转化
replace:把单引号替换成双引号
json和python数据类型对应表
往一个文件中写入多个json串,不再是一个json串,不能直接读取
一行写一个json串,按照行来读取
1 import requests 2 import json 3 from pprint import pprint 4 5 headers = {'User-Agent':'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Mobile Safari/537.36'} 6 post_url = 'https://m.douban.com/rexxar/api/v2/subject_collection/movie_showing/items?os=android&for_mobile=1&start=0&count=18&loc_id=108288&_=0' 7 ret = requests.post(post_url,headers=headers) 8 ret1 = ret.content.decode() 9 10 ret1 = json.loads(ret1) # 把json字符串转换为python类型 11 pprint(ret1) # 美化数据,将字典格式化输入 12 print(type(ret1)) #13 14 15 16 with open('douban.json','w') as f: 17 # f.write(ret1) # 因为ret1是字典类型,所以会报错,写入时必须是str类型 18 # write() argument must be str, not dict 19 f.write(json.dumps(ret1,ensure_ascii=False,indent=2)) 20 # ensure_ascii默认为True,文档中的字符串是ASCII编码,如果报错,此时打开操作要进行encoding设置 21 # indent=2美化格式,每一级缩进2格 22 23 # 其他方法缺陷 24 # f.write(str(ret1)) # 这种情况也可以,但是不能进行以上两个参数设置 25 # eval 嵌套太多不能处理 26 27 28 # with open('douban.json','r',encoding='utf-8') as f: 29 # ret2 = f.read() 30 # ret3 = json.loads(ret2) 31 # # 同样以字典形式读出来 32 # pprint(ret3) 33 # print(type(ret3)) 34 35 36 # 使用json.load()提取类文件对象中的数据 37 with open('douban.json','r') as f: 38 ret4 = json.load(f) 39 print(ret4) 40 print(type(ret4)) 41 42 # 使用json.dump()能够把python类型放入类文件对象中 43 with open('douban1.json','w') as f: 44 json.dump(ret1,f,ensure_ascii=False,indent=2) 45 # 也可以添加参数
练习
爬取豆瓣电视剧列表
三、正则表达式
定义
用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对
字符串的一种过滤逻辑
应用
常用方法
re.compile(编译)
pattern.match(从头找一个)
pattern.search(找一个)
pattern.findall(找所有)
pattern.sub(替换)
常用语法
. 匹配任意除换行符“\n”外的字符,在DOTALL模式下则可以匹配换行符
\ 转义字符使后一个字符变为原来的意思,如果字符串中有字符*需要匹配,可以使用\*或者字符集[*]
[] 取方括号内的一个
\d 匹配数字:[0-9]
\D 匹配非数字:[^\d]
\s 匹配空白字符:[<空格>\t\r\n\f\v]
\S 匹配非空白字符:[^\s]
\w 匹配单词字符:[A-Za-z0-9_]
\W 匹配非单词字符:[^\w]
* 匹配前一个字符0次或无限次
+ 匹配前一个字符1次或无限次
? 匹配前一个字符0次或1次
{m} 匹配前一个字符m次
注意事项
点号默认情况匹配不到'\n'
re.compile()使用时,注意re.S添加位置的有效无效
非贪婪的匹配内容:在贪婪匹配后面添加?,即*?或+?
re.findall(r'a.*bc','a\nbc',re.DOTALL)和re.findall(r'a(.*)bc','a\nbc',re.DOTALL)的区别
不分组使匹配的是全部,分组后匹配的是组内的内容
re.findall('a(.*?)b','str') 能够返回括号中的内容,括号前后的内容起到定位和过滤的效果
原生字符串r,待匹配字符串中有反斜杠的时候,使用r能够忽视反斜杠带来的转义效果
'\s'能够匹配空白字符,不仅仅包含空格,还有‘\t\r\n’
1 import re 2 3 ret = re.findall('.','asd0)+/') 4 print(ret) # ['a', 's', 'd', '0', ')', '+', '/'] 5 ret = re.findall('.','as\nd') # .不能匹配换行符 \n 6 print(ret) # ['a', 's', 'd'] 7 ret = re.findall('.','as\n',re.DOTALL) # 在DTALL模式下可以匹配换行符 \n 8 print(ret) # ['a', 's', '\n'] 9 ret = re.findall('.','as\n',re.S) # re.S相当于re.DOTALL 10 print(ret) # ['a', 's', '\n'] 11 12 ret = re.findall('.','ab.d') 13 print(ret) # ['a', 'b', '.', 'd'] 14 ret = re.findall('\.','ab.d') # \.只匹配原本的. 15 print(ret) # ['.'] 16 17 ret = re.findall('a[bcd]e','ace') 18 print(ret) # ['ace'] 19 ret = re.findall('a[bcd]e','abce') # []只匹配一个相当于或 20 print(ret) # [] 21 ret = re.findall('abe|ace|ade','ace') 22 print(ret) # ['ace'] 23 24 25 26 a = 'chuan1zhi2' 27 ret = re.sub('\d','_',a) 28 print(ret) # chuan_zhi_ 29 ret = re.sub('\d','',a) 30 print(ret) # chuanzhi 31 32 33 p = re.compile('\d') 34 ret = p.findall('chuan1zhi2') 35 print(ret) # ['1', '2'] 36 ret = p.sub('_','chuan1zhi2') 37 print(ret) # chuan_zhi_ 38 39 40 p = re.compile('.') 41 ret = p.findall('\n') 42 print(ret) # [] 43 ret = p.findall('\n',re.S) # 无效 44 print(ret) # [] 45 p = re.compile('.',re.S) # 在编译中使用才有效 46 ret = p.findall('\n') 47 print(ret) # ['\n'] 48 49 50 b = 'a\nb' # 实际上这个\n代表一个字符 51 print(b[1]) # ['\n'] 52 print(len(b)) # 3 53 c = r'a\nb' # 此时表示\和n分别代表一个字符 54 print(c[1]) # \ 在cmd下输出的是'\\',前一个\代表转义符 55 print(len(c)) # 4
练习
爬取内涵段子中的段子
四、xpath和lxml
概述
lxml是一款高性能的python HTML/XML解析器,我们可以利用Xpath,来快速定位特定元素以及获取节点信息
XPATH
XPath是一门在HTML\XML文档中查找信息的语言,可用来在HTML\XML文档中对元素和属性进行遍历。
XML
被设计为传输和存储数据,其焦点是数据的内容
HTML是显示数据以及如何更好的显示数据
XPATH节点选择语法
表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
/div/a[1] 选取属于div子元素的第一个a元素
/div/a[last()] 选取属于div子元素的最后一个a元素
/div/a[last()-1] 选取属于div子元素的倒数第二个a元素
/div/a[position()<3] 选取属于div子元素的最前面的两个为a的元素
//title[@lang] 选取所有拥有名为lang的属性的title元素
//title[@lang='eng'] 选取所有title元素,且这些元素拥有值为eng的lang属性
/bookstore/book[price>35] 选取bookstore元素的所有book元素,且其中的price元素的值须大于35
/bookstore/book[price>35]/title 选取bookstore元素中的book元素的所有title元素,且其中的price元素的值须大于35
XPATH学习重点
获取文本
a/text() 获取a下的文本
a//text() 获取a下的所有文本,包括嵌套标签内的文本
//a[text()='下一页'] 根据文本内容获取标签
@符号
a/@href
//ul[@id='detail-list'] 用标签的属性定位
//
在xpath开始的时候表示从当前html中任意位置开始选择
li//a 表示的是li下任何一个a标签
包含
//div[contains(@class,'i')] 选中class包含i的div标签
xpath返回的是列表,取值时要用索引
lxml
使用入门
导入lxml的etree库
from lxml import etree
利用etree.HTML,将字符串转化为Element对象
Element对象具有xpath的方法
html = etree.HTML(text)
lxml可以自动修正html代码
使用注意
lxml能够修正HTML代码,但是可能会改错
使用etree.tostring观察修改之后的html的样子,根据修改之后的html字符串写xpath
lxml能够接收bytes和str的字符串
提取页面数据的思路
先分组,取到一个包含分组标签的列表
遍历,取其中每一组进行数据的提取,不会造成数据的对应错乱
1 from lxml import etree 2 3 4 # text = '''''' # 缺少一个闭合标签 11 12 ''' 13 html = etree.HTML(text) # 可以将a标签补全 14 print(html) 15 16 print(etree.tostring(html).decode()) # 查看elemen对象中包含的字符串 17 ret1 = html.xpath('//ul/li/a/text()') 18 print(ret1) 19 ret2 = html.xpath('//ul/li/a/@href') 20 print(ret2) 21 22 dict_list = {} 23 for item in ret1: 24 dict_list[item] = ret2[ret1.index(item)] 25 print(dict_list) 26 ''' 27 28 29 # 如果此时text中少第一个内容,那么下面构造的字典也对不上号 30 text = '''5 #
- first item
6 #- second item
7 #- third item
8 #- fourth item
9 #- sixth item
10 #''' # 缺少一个闭合标签 37 38 html = etree.HTML(text) # 可以将a标签补全 39 print(html) 40 41 print(etree.tostring(html).decode()) # 查看elemen对象中包含的字符串 42 ret1 = html.xpath('//ul/li/a/text()') 43 print(ret1) 44 ret2 = html.xpath('//ul/li/a/@href') 45 print(ret2) 46 47 ''' 48 dict_list = {} 49 for item in ret1: 50 dict_list[item] = ret2[ret1.index(item)] 51 print(dict_list) # {'second item': 'www.baidu.com', 'third item': 'www.csdn.net', 'fourth item': 'www.cnblogs.com', 'sixth item': 'www.1211.cn'} 52 对其如下改进,将xpath操作放入内部执行 53 ''' 54 ret1 = html.xpath('//li') 55 print(ret1) # [31
- 32
- second item
33- third item
34- fourth item
35- sixth item
36, 56 57 dict_list = {} 58 for item in ret1: 59 k = item.xpath('./a/text()')[0] if len(item.xpath('./a/text()')) >0 else None 60 v = item.xpath('./a/@href')[0] if len(item.xpath('./a/@href'))>0 else None 61 print(k,v) 62 dict_list[k] = v 63 64 print(dict_list) 65 66 # 以上是自己进行分析解释,下面是课程中的做法 67 68 text = ''', , , ] ''' # 缺少一个闭合标签 75 76 html = etree.HTML(text) 77 ret1 = html.xpath('//ul/li/a/text()') 78 print(ret1) 79 ret2 = html.xpath('//ul/li/a/@href') 80 print(ret2) 81 for item in ret1: 82 dict_list = {} 83 dict_list['title'] = item 84 dict_list['href'] = ret2[ret1.index(item)] 85 print(dict_list) 86 87 print('*'*20,'如果text文本中少一个标题内容如下') 88 text = '''69
- first item
70- second item
71- third item
72- fourth item
73- fifth item
74''' # 缺少一个闭合标签 95 96 html = etree.HTML(text) 97 ''' 98 ret1 = html.xpath('//ul/li/a/text()') 99 print(ret1) 100 ret2 = html.xpath('//ul/li/a/@href') 101 print(ret2) 102 for item in ret1: 103 dict_list = {} 104 dict_list['title'] = item 105 dict_list['href'] = ret2[ret1.index(item)] 106 print(dict_list) 107 改进如下 108 ''' 109 110 111 ret1 = html.xpath('//ul/li') 112 print(ret1) 113 for item in ret1: 114 dict_list = {} 115 dict_list['title'] = item.xpath('./a/text()')[0] if len(item.xpath('./a/text()'))>0 else None 116 dict_list['href'] = item.xpath('./a/@href')[0] if len(item.xpath('./a/@href')) >0 else None 117 print(dict_list)89
- 90
- second item
91- third item
92- fourth item
93- fifth item
94
练习爬糗百和贴吧多页即每页详细内容
1 import requests 2 from lxml import etree 3 import json 4 5 class QiuBaiSpider: 6 def __init__(self,page_num): 7 self.page_num = page_num 8 self.start_url = 'https://www.qiushibaike.com/8hr/page/{}/'.format(page_num) 9 self.heaaders = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'} 10 11 def parse_url(self,url): 12 response = requests.get(url,headers=self.heaaders) 13 return response.content.decode() 14 15 def get_content_list(self,html_str): 16 html = etree.HTML(html_str) 17 li_list = html.xpath("//div[@class='recommend-article']/ul/li[contains(@class,'item')]") 18 # 对li标签进行分组放入列表 19 content_list = [] 20 for li in li_list: 21 dict_list = {} 22 dict_list['title'] = li.xpath("./div[@class='recmd-right']/a/@title")[0] if len(li.xpath("./div[@class='recmd-right']/a/@title"))>0 else None 23 dict_list['href'] = self.start_url + li.xpath("./div[@class='recmd-right']/a/@href")[0] if len(li.xpath("./div[@class='recmd-right']/a/@href"))>0 else None 24 dict_list['userImg'] = self.start_url + li.xpath("./div[@class='recmd-right']/div[contains(@class,'recmd-detail')]//a//img/@src")[0] if len(li.xpath("./div[@class='recmd-right']/div[contains(@class,'recmd-detail')]//a//img/@src"))>0 else None 25 dict_list['name'] = li.xpath("./div[@class='recmd-right']/div[contains(@class,'recmd-detail')]/a/span/text()")[0] if len(li.xpath("./div[@class='recmd-right']/div[contains(@class,'recmd-detail')]/a/span/text()"))>0 else None 26 dict_list['user_content_list'] = self.get_user_content_list(dict_list['href'],[]) 27 content_list.append(dict_list) 28 29 return content_list 30 31 def get_user_content_list(self,href,list_img): # 获取每个标题页内的信息 32 if href is not None: 33 html_str = self.parse_url(href) 34 html = etree.HTML(html_str) 35 img_list = html.xpath("//div[contains(@class,'block')]//img/@src") 36 list_img.extend(img_list) 37 return list_img 38 39 40 41 def save_content(self,content): 42 file_path = "{}.txt".format(self.page_num) 43 with open(file_path,'a',encoding='utf-8') as f: 44 for item in content: 45 f.write(json.dumps(item,ensure_ascii=False,indent=2)) 46 f.write("\n") 47 print('保存成功') 48 49 def run(self): # 实现主要逻辑 50 # 1. start_url 51 # 2. 发送请求,获取响应 52 html_str = self.parse_url(self.start_url) 53 # 3. 对响应分组,提取数据,并获取下一页的url(这里有标准的13页) 54 # 3.1 分组中提取url,标题,用户名,点赞数,评论数,用户名和用户名头像 55 # 3.2 进入url,获取详情页内的图片,提取下一页url 56 # 3.3 请求下一页,依次循环3.2,3.3 57 # 4. 对提取的数据保存 58 content = self.get_content_list(html_str) 59 self.save_content(content) 60 # 5. 请求下一页,依次循环2,3,4 61 62 # "//div[@class='recommend-article']/ul/li[contains(@class,'item')]" 63 if __name__ == '__main__': 64 for i in range(13): 65 qiubai = QiuBaiSpider(i+1) 66 qiubai.run()
五、实现爬虫的套路
准备url
准备start_url
url地址规律不明显,总数不确定
通过代码提取下一页地址
xpath
寻找url地址,部分参数在当前的响应中(比如,当前页码数和总的页码数在当前的响应中)
准备url_list
页面总数明确
url地址规律明显
发送请求,获取响应
添加随机的User-Agent,反反爬虫
添加随机的代理ip,反反爬虫
在对方判断出我们是爬虫之后,应该添加更多的headers字段,包括cookie
cookie的处理可以使用session处理(注意session处理要用全局)
准备一堆能用的cookie组成cookie池
如果不登录
准备刚开始能够请求对方网站的cookie,即接收对方网站设置在response的cookie
下一次请求的时候,使用之前的列表中的cookie来请求
如果登录
准备多个账号
使用程序获取每个账号的cookie
之后请求登录之后才能访问的网站随机的选择cookie
提取数据
确定数据的位置
如果数据在当前的url地址中
提取的是列表页的数据
直接请求列表页的url地址,不用进入详情页
提取的是详情页的数据
1.确定url
2.发送请求
3.提取数据
4.返回
如果数据不在当前的url地址中
在其他的响应中,寻找数据的位置
1.从network中从上往下找
2.使用chrome中的过滤条件,选择处理js,css,img之外的按钮
3.使用chrome的search all file,搜索数字和英文
数据的提取
xpath,从html中提取整块的数据,先分组,之后每一组再提取数据
json,
re,提取max_time,price,html中的json字符串
保存
保存在本地 text,json,csv
保存在数据库 mysql
练习:爬取贴吧(做头发吧,根据视频学习规范的代码)
1 import requests 2 from lxml import etree 3 import json 4 # 【复习字符串拼接、切割、替换】 5 # 【注意图片地址和其他未补全地址】 6 class TiebaSpider: 7 def __init__(self,tieba_name): 8 self.tieba_name = tieba_name 9 self.start_url = 'https://tieba.baidu.com/mo/q----,sz@320_240-1-3---2/m?kw='+tieba_name+'&pn=0' 10 self.part_url = 'https://tieba.baidu.com/mo/q----,sz@320_240-1-3---2/' 11 self.headers = {'User-Agent':'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Mobile Safari/537.36'} 12 13 def parse_url(self,url): 14 print(url) 15 response = requests.get(url,headers=self.headers) 16 return response.content 17 18 def get_content_list(self,html_str): 19 html = etree.HTML(html_str) 20 div_list = html.xpath("//div[contains(@class,'i')]") 21 content_list = [] 22 for div in div_list: 23 item = {} 24 item['title'] = div.xpath('./a/text()')[0] if len(div.xpath('./a/text()')) >0 else None 25 item['href'] = self.part_url + div.xpath('./a/@href')[0] if len(div.xpath('./a/@href'))>0 else None 26 item['img_list'] = self.get_img_list(item['href'],[]) 27 content_list.append(item) 28 # 提取下一页的url地址 29 next_url = self.part_url + html.xpath("//a[text()='下一页']/@href")[0] if len(html.xpath("//a[text()='下一页']/@href"))>0 else None 30 31 return content_list,next_url 32 33 def get_img_list(self,detail_url,total_img_list): 34 # 3.2 请求列表页的url地址,获取详情页的第一页 35 detail_html_str = self.parse_url(detail_url) 36 detail_html = etree.HTML(detail_html_str) 37 # 3.3 提取详情页第一页的图片,提取下一页的地址 38 img_list = detail_html.xpath("//img[@class='BDE_Image']/@src") 39 total_img_list.extend(img_list) 40 # 3.4 请求详情页下一页的地址,进入循环3.2-3.4 41 detail_next_url = detail_html.xpath("//a[text()='下一页']/@href") 42 if len(detail_next_url)>0: 43 detail_next_url = self.part_url + detail_next_url[0] 44 return self.get_img_list(detail_next_url,total_img_list) 45 46 return total_img_list 47 48 def save_content_list(self,content_list): 49 file_path = self.tieba_name + '.txt' 50 with open(file_path,'a',encoding='utf-8') as f: 51 for content in content_list: 52 f.write(json.dumps(content,ensure_ascii=False,indent=2)) 53 f.write('\n') 54 print('保存成功') 55 56 57 def run(self):#实现主要逻辑 58 next_url = self.start_url 59 while next_url is not None: 60 # 1.start_url 61 # 2.发送请求,获取响应 62 html = self.parse_url(next_url) 63 # 3.提取数据,提取下一页的url地址 64 # 3.1 提取列表页的url地址和标题 65 # 3.2 请求列表页的url地址,获取详情页的第一页 66 # 3.3 提取详情页第一页的图片,提取下一页的地址 67 # 3.4 请求详情页下一页的地址,进入循环3.2-3.4 68 content_list,next_url = self.get_content_list(html) 69 # 4.保存 70 self.save_content_list(content_list) 71 # 5.请求下一页的url地址,进入循环2-5步 72 73 if __name__ == '__main__': 74 tieba_spider = TiebaSpider('做头发') 75 tieba_spider.run()
六、csv
七、爬哔哩哔哩视频上的弹幕