day03-爬虫入门

数据提取方法
  一、基础知识
    数据提取
      从响应中获取我们想要的数据的过程
    数据分类
      结构化数据
        类型: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)) # <class 'dict'>
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     # 也可以添加参数
View Code

    练习

      爬取豆瓣电视剧列表

  三、正则表达式
    定义
      用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对
      字符串的一种过滤逻辑
    应用
      常用方法
        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
View Code

    练习

      爬取内涵段子中的段子


  四、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 = '''<div><ul>
  5 #     <li class='item-1'><a href='www.baidu.com'>first item</a></li>
  6 #     <li class='item-1'><a href='www.csdn.net'>second item</a></li>
  7 #     <li class='item-1'><a href='www.cnblogs.com'>third item</a></li>
  8 #     <li class='item-1'><a href='www.1211.cn'>fourth item</a></li>
  9 #     <li class='item-1'><a href='www.douban.com'>sixth item</li>
 10 #     </ul></div>''' # 缺少一个闭合</a>标签
 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 = '''<div><ul>
 31     <li class='item-1'><a href='www.baidu.com'></a></li>
 32     <li class='item-1'><a href='www.csdn.net'>second item</a></li>
 33     <li class='item-1'><a href='www.cnblogs.com'>third item</a></li>
 34     <li class='item-1'><a href='www.1211.cn'>fourth item</a></li>
 35     <li class='item-1'><a href='www.douban.com'>sixth item</li>        
 36     </ul></div>''' # 缺少一个闭合</a>标签
 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) # [<Element li at 0x59d990>, <Element li at 0x59dee0>, <Element li at 0x59d9b8>, <Element li at 0x59d9e0>, <Element li at 0x59d7b0>]
 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 = '''<div><ul>
 69     <li class='item-1'><a href='www.baidu.com'>first item</a></li>
 70     <li class='item-1'><a href='www.csdn.net'>second item</a></li>
 71     <li class='item-1'><a href='www.cnblogs.com'>third item</a></li>
 72     <li class='item-1'><a href='www.1211.cn'>fourth item</a></li>
 73     <li class='item-1'><a href='www.douban.com'>fifth item</li>        
 74     </ul></div>''' # 缺少一个闭合</a>标签
 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 = '''<div><ul>
 89     <li class='item-1'><a href='www.baidu.com'></a></li>
 90     <li class='item-1'><a href='www.csdn.net'>second item</a></li>
 91     <li class='item-1'><a href='www.cnblogs.com'>third item</a></li>
 92     <li class='item-1'><a href='www.1211.cn'>fourth item</a></li>
 93     <li class='item-1'><a href='www.douban.com'>fifth item</li>        
 94     </ul></div>''' # 缺少一个闭合</a>标签
 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)
View Code

    练习爬糗百和贴吧多页即每页详细内容

 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()
View Code

  五、实现爬虫的套路

    准备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()
View Code

  六、csv

  七、爬哔哩哔哩视频上的弹幕

猜你喜欢

转载自www.cnblogs.com/xuedingwangluo/p/10112561.html
今日推荐