爬虫基础(一)

  1. 爬虫基础知识

概念:1.模拟客户端

2.发送网络请求,获取

3.按照规则自动提取数据的程序

分类:1.通用爬虫:搜索引擎(什么都抓,不挑食),百度,谷歌,必应

通用爬虫和聚焦爬虫工作原理:

1.搜索引擎原理

  • 抓取网页

  • 数据存储

  • 预处理

  • 提供检索服务,网站排名

2.聚焦爬虫原理

  • url list

  • 响应内容 提取url

  • 提取数据

  • 入库

3.robots.txt 文件一般放置在网站根目录下

2.HTTP和HTTPS内容

HTTP:超文本传输协议,默认端口号:80

HTTPS:HTTP+SSL(安全套接字层) 默认端口号:443

HTTPS和HTTP更安全,但是性能更低

url的格式:

浏览器地址栏的链接地址https://www.baidu.com/

形式 scheme://host[:port#]/path/.../[?query-string][#anchor]

scheme:协议(例如:http,https,ftp)

host:服务器的IP地址或者域名

port:服务器的端口(如果是走协议默认端口,80 or443)

path:访问资源的路径

http://www.cnblogs.com/be-saber/p/4734951.htm

  • 浏览器会根据html标签,自动再次请求,获取图片,css,js等内容,叫做渲染

  • 爬虫只会根据url地址,获取网页地址,发送请求,获取响应

重点:浏览器访问一个网站,在获取网站源码(HTML)之后,会根据源码上的链接加载图片,js ,css文件等,我们称之为渲染,而爬虫只会请求指定的url对应的网页源码,不会自动加载css,js,图片

3.HTTP请求报文的格式

对于爬虫,最重要的是User-agent,其次:cookie, referer ,accept等

# 操作系统的版本,浏览器内核,版本信息等
# 白氏客户端是谁?
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
# 表示从哪来?
# 也是CSRF防护手段之一
Referer: https://www.baidu.com/

总结:

爬虫代码中:必须要用的用户代理,其次,referer和cookie等请求头信息

  1. HOST(主机和端口号)

  1. Connection(链接类型)

  1. Upgrade-Insecure-Requests(升级为HTTPS请求)

  1. User-Agent(用户代码)

服务器能够识别客户使用的操作系统及版本,CPU类型,浏览器及版本,浏览器渲染引擎,浏览器语言,浏览器插件等

  1. Accept(传输文件类型)

  1. Referer

页面跳转处,从何处来到当前页面,可用于防爬和防盗链

  1. Accept-Encoding

浏览器支持的编码类型,主要的编码格式就是压缩格式 gizp compress deflate

  1. Cookie

用户进行状态保持,通常也可用于识别用户身份

  1. x-requested-with:

XMLHttpRequest xhr是Ajax 异步请求

4.get与post方法

get方法

  • 用来请求资源

  • 在url中传输实体数据

  • 传输的数据量小(受浏览器限制)

post方法

  • 用来传输实体数据(本质上更专业)

  • 在实体中传输数据

  • 传输的数据量大

6.响应状态码(status code)

200:成功

客户端---请求正常处理则返回200 ok------服务器

  • 爬虫代码中:不能百分百的信任服务器返回的状态码,必须以服务器返回的响应中是否有数据为准,为唯一标准。

  • 如果浏览器中能看到数据,爬虫代码中获取不到,对比代码和浏览器的请求的区别。

  • 如果遇到异常状态码,对比代码中的请求信息,和浏览器中的请求信息(请求头)和区别。

7.字符编码说明

str类型和bytes类型

  • bytes:二进制

  • 互联网上数据的都是以二进制的方式传输的

  • str:unicode的呈现形式

Unicode UTF8 ASCLL的补充

字符集(Character set)是多个字符的集合

字符集包括:ASCLL字符集,GB2312字符集,GB18030字符集,Unicode字符集等

ASCLL编码是1个字节,而Unicode编码通常是2个字节(UCS-2)

UTF-8是Unicode的实现方式之一,UTF-8是它是一种变长的编码方式,可以是1,2,3个字节

str bytes如何转化

  • 字符串数据使用encode方法转化为bytes

  • Bytes类型数据通过decode转化为字符串类型数据

  • 编码方式是默认是utf-8,如果需要选定编码方式,编码方式解码方式必须一样,否则就会出现乱码

8.requests基本使用

发送简答请求

需求:通过requests向百度首页发送请求,获取百度首页的数据

response=requests.get(url)

response的常用属性

  • response.txt 获取str类型的响应

  • response.context 获取bytes类型的响应

  • response.status_code 获取状态码

  • response.headers 获取响应头

  • response.request 获取响应对应的请求

response.text

  • 类型:str

  • 解码类型:根据HTTPS头部对响应的编码作出有根据的推测,推测的文本编码

  • 如何修改编码方式:response.encoding='gbk'

response.content

  • 类型:bytes

  • 解码类型:没有指定

  • 如何修改编码方式:response.content.decode('utf8')

更推荐使用response.content.decode('utf8')的方式获取响应的html页面

headers的形式:字典

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/110.0.0.0 Safari/537.36'}
# 发送请求,伪装成浏览器
resp2 = requests.get(url, headers=headers)
with open('baidu4.html', 'w') as f:
    f.write(resp2.content.decode('utf8'))

9.发送带参数的请求

  • 参数的形式:字典

  • kw={'wd':'python'}

  • 用法:requests.get(url,params=kw)

10.爬取百度贴吧案例

# 单页爬取
"""
爬虫代码实现步骤:
# 1.要准备其实url地址和请求头信息
# 2.发送请求,获取响应
# 3.保存信息
# 4.定义入口函数

"""
import requests


class TuPian(object):
    def __init__(self):
        self.url = 'https://m.baidu.com/sf/vsearch?pd=image_content&word=%C3%C0%CD%BC&tn=vsearch&atn=page'
        self.headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit'
                                      '/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}

    def get_data(self):
        resp = requests.get(self.url, self.headers)
        return resp.content.decode('utf-8')

    def save(self, data):
        with open('tupian.html', 'w', encoding='utf-8') as f:
            f.write(data)

    def run(self):
        data = self.get_data()
        self.save(data)


if __name__ == '__main__':
    tupian = TuPian()
    tupian.run()
"""
爬虫代码实现步骤:
# 1.要准备其实url地址和请求头信息
# 2.发送请求,获取响应
# 3.保存信息
# 4.定义入口函数

"""
#------爬取吧的页数---------

import requests


class TieBa(object):
    def __init__(self, pn):
        self.url = 'https://tieba.baidu.com/f?kw=%E4%B8%8A%E6%B5%B7%E8%B4%B4&ie=utf-8&pn='
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
                          ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
        self.url_list = [self.url + str(i * 50) for i in range(pn)]
        print(self.url_list)

    def get_data(self, url):
        # 发送请求,获取响应
        resp = requests.get(url, self.headers)
        return resp.content.decode('utf-8')

    def save_data(self, data, index):
        # 保存数据
        file_name = 'tieba_' + str(index) + '.html'
        with open(file_name, 'w', encoding='utf-8') as f:
            f.write(data)

    def run(self):
        # 入口函数,在类的内部,实现各个功能函数之间的协调调用
        # 遍历url列表,把没页的url传给发送请求的函数
        for url in self.url_list:
            data = self.get_data(url)
            index = self.url_list.index(url)
            print('index=',index)
            self.save_data(data, index)


if __name__ == '__main__':
    tieba = TieBa(3)
    tieba.run()
#----爬取不同的吧,且不覆盖之前爬取的吧-------------

import requests


class TieBa(object):
    def __init__(self, name,pn):  # name 为吧名字
        self.url = 'https://tieba.baidu.com/f?kw={}ie=utf-8&pn='.format(name)
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
                          ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
        self.url_list = [self.url + str(i * 50) for i in range(pn)]
        self.name = name
        print(self.url_list)

    def get_data(self, url):
        # 发送请求,获取响应
        resp = requests.get(url, self.headers)
        return resp.content.decode('utf-8')

    def save_data(self, data, index):
        # 保存数据
        file_name = self.name + str(index) + '.html'
        with open(file_name, 'w', encoding='utf-8') as f:
            f.write(data)

    def run(self):
        # 入口函数,在类的内部,实现各个功能函数之间的协调调用
        # 遍历url列表,把没页的url传给发送请求的函数
        for url in self.url_list:
            data = self.get_data(url)
            index = self.url_list.index(url)
            print('index=', index)
            self.save_data(data, index)


if __name__ == '__main__':
    tieba = TieBa('李毅', 3)
    tieba.run()
#----------终端命令输入---------

import requests


class TieBa(object):
    def __init__(self, name, pn):  # name 为吧名字
        self.url = 'https://tieba.baidu.com/f?kw={}ie=utf-8&pn='.format(name)
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
                          ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
        self.url_list = [self.url + str(i * 50) for i in range(pn)]
        self.name = name
        print(self.url_list)

    def get_data(self, url):
        # 发送请求,获取响应
        resp = requests.get(url, self.headers)
        return resp.content.decode('utf-8')

    def save_data(self, data, index):
        # 保存数据
        file_name = self.name + str(index) + '.html'
        with open(file_name, 'w', encoding='utf-8') as f:
            f.write(data)

    def run(self):
        # 入口函数,在类的内部,实现各个功能函数之间的协调调用
        # 遍历url列表,把没页的url传给发送请求的函数
        for url in self.url_list:
            data = self.get_data(url)
            index = self.url_list.index(url)
            print('index=', index)
            self.save_data(data, index)


if __name__ == '__main__':
    import sys  # 获取程序外的输入
    import codecs

    sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())

    # print('argv[0]=', sys.argv[0])  # 传文件名
    # print('argv[1]=', sys.argv[1])  # 传名字 李毅
    # print('argv[2]=', sys.argv[2])  # 次数 3
    name = sys.argv[1]
    pn = int(sys.argv[2])
    tieba = TieBa(name, pn)
    tieba.run()

11.Requests深入

发送POST请求

  • 登录注册(POST比GET更安全)

  • 绝大多数的登录会使用post请求,极少数网站仍然在使用get请求进行登录

  • 向服务器传输的数据量比较多的时候,或者向服务器传输大文件

  • 所以同样的,我们的爬虫也需要在这两个地方会去模拟浏览器发送post请求

状态保持cookies

  1. cookie和session

二者区别:

  • cookie数据存放在客户的浏览器上,session数据放在服务器

  • cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗(使用用户的cookie获取相关信息)。

  • session会在一定时间内保存在服务器。当访问增多,会比较占用服务的性能。

  • 单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点保存的cookie的信息

1.requests模块中的Session,能够实现自动保存服务器返回的会话信息

  1. 方向代理:代理的服务器,隐藏了服务器

  1. 正向代理:代理的客户端,隐藏了客户端

import requests

"""
代理ip的使用,在工作中必须写爬虫代码,必须使用代理ip(不止一个ip),使用高匿代理
"""
url = 'http://www.baidu.com'
# 定义字典,保存代理字典,代理ip使用付费的,不会只用一个
proxy = {'http': ''}
# 不使用代理ip,发送请求
resp1 = requests.get(url)

print(resp1.status_code)
# 使用代理ip,发送请求,免费代理Ip不好用
resp2 = requests.get(url, proxies=proxy)
print(resp2.status_code)

Requests小技巧

  1. cookiesjar与字典之间的转换

requests.utils.dict_from_cookiejar 把cookiejar对象转化为字典格式的cookies

requests.utils.cookiejar_from_dict 把字典格式的cookies转换成cookiejar对象

import requests

# cookiejar和字典之间的转换
# 作用:动态获取服务器返回cookie信息,代码中动态处理,不是在请求头中固定写死cookie信息
url = 'http://www.baidu.com'
resp = requests.get(url)
print(resp.cookies)
cookie_jar = resp.cookies
cookie_dict = requests.utils.dict_from_cookiejar(cookie_jar)
print(cookie_dict)
# 把字典转换为cookie对象
print(requests.utils.cookiejar_from_dict(cookie_dict))
  1. 请求SSL整数验证

使用场景:

Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样。SSL验证默认是开启的,如果证书验证失败,Requests会抛出SSLError

使用方式:response=requests.get('https://12306.cn/mormhweb/'verify=False)

import requests

# 只有在访问https类型的网站才会遇到
url = 'https://sam.huat.edu.cn:8433/selfservice/'
# 默认开启CA证书认证
# resp = requests.get(url)
# 关闭认证
resp = requests.get(url, verify=False)
print(resp.status_code)
  1. 设置超时

使用场景:有些站点或者代理反应慢,严重降低效率,这时候可以设置超时

使用方式:response=requests.get(url,timeout=10)

import requests

url = 'http://www.baidu.com'
# 定义字典,保存代理ip,代理ip使用付费的,不会只用一个
proxy = {'http': 'http://123.182.58.46:8089'}
# 使用代理ip,发送请求,免费代理ip不好用
# timeout表示发送请求的超时时间,单位秒,有一定的网络延时,不是秒表
# timeout作用:可以用来测试代理IP是否好用
resp2 = requests.get(url, proxies=proxy, timeout=5)
print(resp2.status_code)

12.数据提取与数据分类

  • 数据化结构:json,xml等

  • 处理方法:转化为python数据类型

注意:xml:可扩展标记语言,标签可以自定义

html:标签不能自定义

# 标签都是预定好的,只能选择用或者不用
# 作用:主要用来展示数据

 
 
   退出
    
 

# xml标签可以自定义,微信使用xml传输数据,包括:表情,文字,红包,视频,语言等数据:
# 作用:主要用来传输数据,传输账号和密码,python处理xml数据,pip install xmltodict模块,parse和unparse

  zhangsan
  123456
  
# 多数网站都是使用json较多,轻量级的数据交互格式
'{'name':'zhangsan','password':'123456'}'
  • 把json格式字符串转换为Python字典类型很简单,所以爬虫中,如果我们能够找到返回json数据格式字符串的url,就会尽量使用这种url

  • dumps 把字典转json

  • loads 把json转成字典

13.豆瓣电影

  • 数据包查找:

  1. 找数据包类型document,看第一个,根据网页的数据的关键字,搜索数据包是否有数据

  1. 如果第一个数据包没有,后面的数据包较多,直接找xhr

# 通过浏览器测试,网页中能够展示数据包的url地址
# https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A
# 实现步骤:
# 1.准备起始的url和请求头
# 2.发送请求,获取响应
# 3.解析响应,提取数据
# 4.保存数据
# 5.程序运行入口
# """
#
# class DouBan(object):
# #     def __init__(self):
# #         self.url = 'https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A'
# #         self.headers = {
# #             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
# #                           'Chrome/110.0.0.0 Safari/537.36'}
# #         # 打开文件
# #         self.file = open('douban.json', 'w', encoding='utf-8')
# #
# #     def get_data(self):
# #         # 发送请求,获取响应
# #         resp = requests.get(self.url, headers=self.headers)
# #         return resp.content.decode()
# #
# #     def parse_data(self, data):
# #         # 解析响应,提取数据
# #         results = json.loads(data)
# #         # print(results)
# #         results_list = results['subjects']
# #         # 定义列表,用来保存提取的数据
# #         data_list = []
# #         # 遍历列表数据,获取每部电影的信息,名称,评分,url
# #         for item in results_list:
# #             temp = {}
# #             temp['title'] = item['title']
# #             temp['url'] = item['url']
# #             data_list.append(temp)
# #         return data_list
# #
# #     def save_date(self, data_list):
# #         # 保存数据
# #         # 不能直接写入字典
# #         for data in data_list:
# #             # 不是ascii编码,在每条数据的后面,加上逗号和换行符
# #             json_data = json.dumps(data, ensure_ascii=False) + '\n'
# #             self.file.write(json_data)
# #
# #     def __del__(self):
# #         # 解构方法,最后执行(关闭文件)
# #         self.file.close()
# #
# #     def run(self):
# #         data = self.get_data()
# #         data_list = self.parse_data(data)
# #         self.save_date(data_list)
# #
# #
# # if __name__ == '__main__':
# #     douban = DouBan()
# #     douban.run()

14.爱词霸

"""
http://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_web_fanyi&sign=2ad53c323affb5ad
POST
from: auto
to: en
q: 中国
content1
: # 中文翻译英文
{status: 1,…}
content: 
{from: "zh", to: "en", vendor: "ciba", out: "China", reqid: "438b8144-4003-47fc-a40d-7f9956dea749",…}
status: 1
 # 英文翻译中文
{status: 1,…}
content:
{from: "en", to: "zh", vendor: "ciba", out: "老鼠", reqid: "0cd50ed7-6c11-4b04-9c01-00f9ff1b1af4",…}
1.准备起始的url和请求头
2.发送请求,获取响应
3.解析响应,提取翻译结果
5.程序运行入口
"""

import requests
import json


class Translation(object):
    def __init__(self, dw):
        self.url = 'http://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_web_fanyi&sign' \
                   '=2ad53c323affb5ad '
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/110.0.0.0 Safari/537.36'}
        self.data = {'from': 'zh', 'to': 'en', 'q': dw}

    def get_data(self):
        resp = requests.post(self.url, headers=self.headers, data=self.data)
        return resp.content.decode('utf-8')

    def parse_data(self, data):
        # 把响应的json字符串改成字典,获取翻译结果
        # 异常处理:
        results = json.loads(data)
        # try:
            # 中文翻译成英文
        result_list = results['content']['out']
        # except:
        #     result_list = results['content']['out']
        print(result_list)

    def run(self):
        data = self.get_data()
        self.parse_data(data)


if __name__ == '__main__':
    translation = Translation('翻译')
    translation.run()

15.XPATH和LXML类库

  1. 可以利用XPath,来快速的定位特定元素以及获取节点信息

  1. XPATH(元素==标签==节点)

在HTML\XML文档中查找信息的语言,可以来在HTML\XML文档中对元素和属性进行遍历

  1. lxml是python的模块,在python代码中书写xpath语言,定位网页数据。

  1. xPath Helper:是个工具

16.正则表达式(复习)

# -------------------------
import re

from django.contrib.admin.templatetags.admin_list import results

# data = 'abc'
# print(re.findall('abc', data))  # ['abc']
# print(re.findall('a.c', data))  # ['abc']
# data = 'a\nc'
# print(re.findall('a.c', data))  # []
# # DOTALL表示可以匹配换行符
# data = 'a\nc'
# print(re.findall('a.c', data, re.DOTALL))  # ['a\nc']
# print(re.findall('a.c', data, re.S))  # ['a\nc']
# /斜线  \反斜线
# data = 'a\c'
# print(len(data))
# print(re.findall('a.c', data))  # ['a\\c']
# []字符集,范围内的字符
# data = 'abc adc afc'
# print(re.findall('a[bf]c', data))
# 预定义字符集
# data = 'itcast 2020 python36'
# 匹配字符串中的数字
# print(re.findall('\d', data))  # ['2', '0', '2', '0', '3', '6']
# # 匹配非数字
# print(re.findall('\D', data))  # ['i', 't', 'c', 'a', 's', 't', ' ', ' ', 'p', 'y', 't', 'h', 'o', 'n']
# # 匹配非空白字符
# print(re.findall('\S', data))  #['i', 't', 'c', 'a', 's', 't', '2', '0', '2', '0', 'p', 'y', 't', 'h', 'o', 'n', '3', '6']
# # 匹配空白字符
# print(re.findall('\s', data)) # [' ', ' ']
# # 匹配单词字符,字母,数字,下划线
# print(re.findall('\w',data)) #['i', 't', 'c', 'a', 's', 't', '2', '0', '2', '0', 'p', 'y', 't', 'h', 'o', 'n', '3', '6']
# print(re.findall('\W',data)) #[' ', ' ']
# 量词
data = 'itcast 你好2020 python36'
# *表示匹配前个字符0或n次
# print(re.findall('t*', data))  # ['', 't', '', '', '', 't', '', '', '', '', '', '', '', '', 't', '', '', '', '', '', '']
# print(re.findall('t+', data))  # ['t', 't', 't']
# 贪婪(尽可能多匹配)和非贪婪(限制匹配的次数,非贪婪)
# print(re.findall('t?', data))  # ['', 't', '', '', '', 't', '', '', '', '', '', '', '', '', 't', '', '', '', '', '', '']
# 爬虫代码中,多数情况下使用.*?
# print(re.findall('itcast(.*?)2020', data))  # [' 你好']
# compile表示编译
# 每执行一次findall,都会编译一次规则表达式,执行多次,编译多次
# print(re.findall('\d+', data))
# # 建议使用第二种,匹配效率比第一种要高
# r = re.compile('\d+')
# # 通过compile方法,得到编译的规则对象,直接调用findall,如果findall,执行多次只编写一次编译,多次使用
# print(r.findall(data))

用正则表达式爬取数据(新闻)

"""

https://36kr.com/
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
实现步骤:
1.准备起始的url和请求头信息
2.发送请求,获取响应
3.解析响应,提取数据,返回提取的数据列表
4.保存数据列表
5.代码启动
36k新闻:提取标签,摘要,url地址,图片链接
"""

import requests
import re


class XinWen(object):
    def __init__(self):
        self.url = 'https://36kr.com/'
        self.headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/110.0.0.0 Safari/537.36'}
        self.file = open('xinwen2', 'w', encoding='utf-8')

    def get_data(self):
        resp = requests.get(self.url, headers=self.headers)
        return resp.content.decode()

    def parse_data(self, data):
        results = re.findall('', data)[0]
        # 把提取的原始新闻数据,保存文件,便于查看数据的结构
        # with open('news.json', 'w', encoding='utf-8') as f:
        #     f.write(results)
        # 把原始的新闻json数据,转成Python字典
        result_dict = json.loads(results)
        # 提取新闻列表数据
        list_results = result_dict['homeData']['data']['homeFlow']['data']['itemList']
        # 定义列表,用来保存提取的新闻数据
        data_list = []
        for item in list_results:
            # 网页中itemType不同有10,60,5000
            temp = {}
            if item['itemType'] == 10:
                temp['widgetTitle'] = item['templateMaterial']['widgetTitle']
                temp['summary'] = item['templateMaterial']['summary']
                temp['widgetImage'] = item['templateMaterial']['widgetImage']
                temp['author'] = item['templateMaterial']['authorName']
                print(temp)
                data_list.append(temp)
        return data_list

    def save_data(self, data_list):
        for data in data_list:
            json_data = json.dumps(data, ensure_ascii=False, ) + '\n'
            self.file.write(json_data)

    def __del__(self):
        self.file.close()

    def run(self):
        data = self.get_data()
        data_list = self.parse_data(data)
        self.save_data(data_list)


if __name__ == '__main__':
    xinwen = XinWen()
    xinwen.run()

17.Xpath的基础语法

  • 网页html标签数据的体现形式:xPath默认提取的是标签中的文本内容

  • 选中的标签会添加属性class='xh-highlight'

  • 在爬虫代码中,xpath的使用,先从浏览器中copy,在手动修改xpath

# 数据保存在标签中
这是一个div
# 标签中的数据:‘这是一个a标签’ # 标签中的属性;href < a href='...'>这是一个a标签 #绝对路径 /html/head/title # 相对路径 //title # 绝对路径:如果使用绝对路劲,任意层级发生变化,都会导致无法提取数据: 相对路径:爬虫代码中,建议使用相对路径 # 修饰属性:@根据属性查找具体的标签 /html/head/meta[@content='always'] # 修饰属性:@,提取标签中的属性内容 //div[@id='page']/div/a/@href ####提取下一页链接:不建议直接使用last,包含contains() //div[@id='page']/div/a[last()]/@href

18.lxml模块的使用

# 导入lxml中的etree,需要安装使用
# 代码中使用lxml:
# 1.首先通过浏览器复制xpath语句,在xpath_helper中测试能否提取数据
# 2.多数情况下后,需要修改复制xpath语句
# 3.把xpath语句,复制到代码中使用;
# 定义变量

19.好段子网页爬取数据(案例一)

import json

import requests
from lxml import etree


class DuanZi(object):
    def __init__(self):
        # 初始化url和请求头
        self.url = 'http://haoduanzi.com/?1_{}'
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/110.0.0.0 Safari/537.36'}
        self.file = open('haoduanzi3.html', 'w', encoding='utf-8')
        # 定义空列表,用来保存url
        self.url_list = []

    def generate_url_list(self):
        # 生成不同页数的url信息
        self.url_list = [self.url.format(i) for i in range(1, 10)]

    def get_data(self, url):
        resp = requests.get(url, headers=self.headers)
        return resp.content.decode('utf-8')

    def parse_data(self, data):
        # 解析响应内容,提取段子的标题和内容
        html = etree.HTML(data)
        # not是官方自带函数
        node_list = html.xpath("// *[ @ id = 'LR'] / div / div[2] / ul / li[not(@class='ad')]")
        data_list = []
        # 遍历节点列表,提取数据
        for node in node_list:
            temp = {}
            temp['title'] = node.xpath('/div[1]/h2/text()')
            temp['content'] = node.xpath('div[2]/a/p/text() | div[2]/a/text()')
            data_list.append(temp)
            # 返回列表
        return data_list

    def save_data(self, data_list):
        # 遍历数据列表,把每条字典数据,转成json,保存文件
        for data in data_list:
            json_data = json.dumps(data, ensure_ascii=False) + '\n'
            self.file.write(json_data)

    def __del__(self):
        self.file.close()

    def run(self):
        self.generate_url_list()
        for url in self.url_list:
            data = self.get_data(url)
            data_list = self.parse_data(data)
            self.save_data(data_list)


if __name__ == '__main__':
    duanzi = DuanZi()
    duanzi.run()

总结:

  • 浏览器中测试的xpath能够取出页面,代码中不一定(lxml模块的语法和xpath在浏览器中的使用有区别);

  • 如果代码中xpath提取不到,首先把响应写入文件,是否有数据,如果有数据,说明是lxml模块和xpath语法不同

  • 把响应写入文件,是否有数据,如果没有数据,可能被网站反爬了,模拟浏览器更多的请求头:

20.百度贴吧(案例二)

"""
需求:
1.完成某个百度贴吧的所有贴
2.使用xpath进行定位
3.完成翻页功能
4.下载详情页面中的图片


1.第一次网络请求:div/div[2]/div[1]/div[1]/a/@href
https://tieba.baidu.com/f?ie=utf-8&kw=90%E5%90%8E%E7%BE%8E%E5%A5%B3
node_list=//*[@id="thread_list"]/li[@class="j_thread_list clearfix thread_item_box"]
提取每个帖子的链接,得到贴吧的列表,下一页的链接
https://tieba.baidu.com/ + div/div[2]/div[1]/div[1]/a/@href
下一页的链接,缺少https:/div[2]/div[1]/div[1]/a/@href
'https:'+ //*[@id="frs_list_pager"]/a[contains(text(),'下')]/@href
2.遍历帖子链接,发送请求,进入每个帖子中,提取图片列表
//img[@class="BDE_Image"]/@src
3.遍历图片列表,下载每张图片,保存图片

4.每一页图片处理完成后,翻页
"""
import requests
from lxml import etree
import os


class TuPian(object):
    def __init__(self):
        self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw=90%E5%90%8E%E7%BE%8E%E5%A5%B3'
        self.headers = {
            # 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
            #               'Chrome/27.0.1453.94 Safari/537.36'}
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'}

    def get_data(self, url):
        resp = requests.get(url, headers=self.headers)
        # 保存响应内容,用来测试
        # with open('tupian11.html', 'w', encoding='utf-8') as f:
        #     f.write(resp.content.decode())
        print(resp.content)
        return resp.content
        pass

    def parse_data(self, data):
        # 解析响应,返回帖子列表
        html = etree.HTML(data)
        node_list = html.xpath('//*[@id="thread_list"]/li[@class="j_thread_list clearfix thread_item_box"]')
        print(len(node_list))
        # 遍历列表
        tieba_list = []
        # 遍历节点列表,提取数据
        for node in node_list:
            temp = {}
            temp['title_url'] = 'https://tieba.baidu.com/' + node.xpath('./div/div[2]/div[1]/div[1]/a/@href"]')[0]
            print(temp)
            tieba_list.append(temp)
        # 提取下一页的链接,不能根据下标取链接,如果最后一页,为None
        next_url = html.xpath("//*[@id='frs_list_pager']/a[contains(text(),'下一页')]/@href")
        return node_list, next_url
        pass

    def parse_detail(self, datail_data):
        # 解析帖子详情内容,返回图片列表
        html = etree.HTML(datail_data)
        image_list = html.xpath('//img[@class="BDE_Image"]/@src')
        return image_list

    def download(self, image_list):
        # 遍历图片列表,发送请求,下载图片
        # 通过代码,创建文件夹,用来保存图片,不能每
        for img_url in image_list:
            image_bytes = self.get_data(img_url)
            image_name = 'image' + os.sep + img_url.split('/')[-1]
            # 为了让代码,既可以
            with open(image_name, 'wb', encoding='utf-8') as f:
                f.write(image_bytes)
        pass

    def run(self):
        # 第一次请求
        url = self.url
        # 解析响应内容,获取贴吧列表,下一页链接
        while True:
            data = self.get_data(url)
            next_url, tieba_list = self.parse_data(data)
            # 遍历贴吧列表,对每个帖子发送请求,获取图片列表
            for tieba in tieba_list:
                detail_data = self.get_data(tieba['url'])
                image_list = self.parse_detail(detail_data)
                self.download(image_list)
            # 如果一个页面的贴吧列巴处理完,应该翻页
            if not next_url:
                break
            else:
                url = 'https:' + next_url

        pass


if __name__ == '__main__':
    tupian = TuPian()
    tupian.run()

你可能感兴趣的:(爬虫,python,开发语言,pycharm)