2 爬虫 数据解析(bs4,XPath) robots协议 cookie反爬 代理反爬

爬虫

1 数据解析

1.1 介绍

1.1.1 概念即作用

数据解析就是将一组数据中的局部数据进行提取,用于实现聚焦爬虫。
聚焦爬虫是建立在通用爬虫的基础上,从通用爬虫获取的整个源码数据中提取出指定的数据。

1.1.2 聚焦爬虫的流程
  1. 指定url
  2. 发起请求
  3. 获取响应数据
  4. 数据解析
  5. 持久化存储

常用的数据解析方式包括:
正则匹配、bs4、XPath(常用)、PyQuery等。

1.1.3 数据解析原理
  1. html的主要作用是数据展示。
  2. 待解析的数据其实都存储在html标签之中或标签的属性中
  3. 通用原理:
    (1) 定位标签;
    (2) 获取标签中的数据或者标签的属性值。

1.2 bs4解析

1.2.1 bs4数据解析步骤
  1. 实例化BeautifulSoup对象,将被解析的页面数据加载到BeautifulSoup对象中;
  2. 调用BeautifulSoup对象中的方法和属性,进行标签定位和相关数据的提取。
1.2.2 实例化BeautifulSoup对象

方式1:BeautifulSoup(fp, 'lxml')
用于对本地存储的html文件进行数据解析;

方式2:BeautifulSoup(page_text, 'lxml')
用于对互联网上请求到的页面资源进行数据解析。

解析器一般选用lxml

1.2.3 标签定位
标签定位
bs_obj.tagName
定位到第一次出现的指定标签。

属性定位
bs_obj.find('tagName', attrName='attrValue')
find只可以定位到第一次出现的指定标签。

bs_obj.find_all('tagName', attrName='attrValue')
find_all可以定位到符合条件的所有标签,返回值类型是列表。

选择器定位
bs_obj.select('选择器')
id选择器、类选择器、层级选择器
注:层级选择器中
大于号:表示相邻层级
空格:表示中间间隔多个层级
fp = open('./test.html', 'r', encoding='utf-8')
bs_obj = BeautifulSoup(fp, 'lxml')  # 实例化BeautifulSoup对象

# 标签定位
bs_obj.div  # 第一次出现的div标签
bs_obj.find('div', class_="song")
bs_obj.find_all('div', class_='song')
bs_obj.find_all('a', id='feng')

bs_obj.select('#feng')  # id选择器
bs_obj.select('.song')  # 类选择器
bs_obj.select('.tang > ul > li > a')  # 层级选择器
bs_obj.select('.tang a')
1.2.4 提取数据
取文本
tag.string: 获取标签直系的文本内容
tag.text: 获取标签下所有的文本内容

取属性
tag['attrName']
bs_obj.find('a', id="feng").text
bs_obj.find('a', id="feng").string

bs_obj.find('a', id="feng")['href']
1.2.5 案例 爬取文学名著

目标:从诗词名句网爬取《三国演义》
《三国演义》:http://www.shicimingju.com/book/sanguoyanyi.html

import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
catalogue_url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
catalogue_text = requests.get(url=catalogue_url, headers=headers).text

# 解析各章节标题 + 详情页的url
catalogue_bs_obj = BeautifulSoup(catalogue_text, 'lxml')
a_list = catalogue_bs_obj.select('.book-mulu > ul > li > a')
for each_a in a_list:
    each_title = each_a.string
    each_detail_url = 'http://www.shicimingju.com' + each_a['href']
    each_detail_text = requests.get(url=each_detail_url, headers=headers).text
    
    # 解析各章节的内容
    each_detail_bs_obj = BeautifulSoup(each_detail_text, 'lxml')
    each_detail_content = each_detail_bs_obj.find('div', class_='chapter_content').text
    each_filepath = './sgyy/{title}.txt'.format(title=each_title)
    with open(each_filepath, 'w', encoding='utf-8') as fp:
        fp.write(each_detail_content)
    print('《{title}》下载保存成功!'.format(title=each_title))

1.3 XPath解析

1.3.1 XPath数据解析步骤

html标签是基于树状结构的。

  1. 实例化etree对象,将待解析的数据加载到该对象中;
  2. 调用etree对象中的xpath方法,结合着不同形式的xpath表达式进行标签定位和数据提取。
1.3.2 实例化etree对象

方式1:etree.parse('filename')
用于对本地存储的数据进行解析。

方式2:etree.HTML('page_text')
用于对互联网上请求到的页面资源进行数据解析。

1.3.3 标签定位

tree_obj.xpath(xpath表达式)返回的数据类型是列表。

标签定位
对于最左侧的`/`,一定要从根标签开始定位;
对于最左侧的`//`,可以从任意位置的标签开始定位;
对于非最左侧的`/`,表示相邻层级;
对于非最左侧的`//`,表示中间间隔多个层级。

属性定位
tree_obj.xpath('//tagName[@attrName="attrValue"]')

索引定位
tree_obj.xpath('//tagName[index]')
注意索引从1开始,返回值类型仍是列表。

模糊定位
tree_obj.xpath('//tagName[contains(@attrName, "attrValue")]')
tree_obj.xpath('//tagName[starts-with(@attrName, "attrValue")]')
from lxml import etree

tree_obj = etree.parse('./test.html')
# 定位title标签
tree_obj.xpath('/html/head/title')  # []
tree_obj.xpath('/html//title')
tree_obj.xpath('//title')

# 属性定位
tree_obj.xpath('//div[@class="song"]')

# 索引定位
tree_obj.xpath('//div[2]')

# 模糊定位
tree_obj.xpath('//div[contains(@class, "ng")]')
tree_obj.xpath('//div[starts-with(@class, "ta")]')
1.3.4 数据提取

取文本

/text():用于将标签中直系的文本内容取出,返回的列表只有一个元素;
//text():用于将标签中所有的文本内容取出;
tree_obj.xpath('//a[@id="feng"]/text()')
# ['凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘']
tree_obj.xpath('//a[@id="feng"]/text()')[0]
# '凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘'

tree_obj.xpath('//div[2]//text()')
# ['\n\t\t', '李清照', '\n\t\t', '王安石', '\n\t\t'...]
''.join(tree_obj.xpath('//div[2]//text()'))
# '\n\t\t李清照\n\t\t王安石\n\t\t...'

取属性

/@attrName
tree_obj.xpath('//a[@id="feng"]/@href')
# ['http://www.haha.com']
1.3.5 案例 爬取糗事百科的段子

糗事百科的段子:https://www.qiushibaike.com/text/

爬取首页

import requests
from lxml import etree

headers = {
    'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
main_url = 'https://www.qiushibaike.com/text/'
main_text = requests.get(url=main_url, headers=headers).text

tree_obj = etree.HTML(main_text)
# 标签定位
div_list = tree_obj.xpath('//div[@class="col1 old-style-col1"]/div')
for each_div in div_list:
    # 局部数据解析,从每一个div中进一步进行数据解析
    author_str = each_div.xpath('./div[1]/a[2]/h2/text()')[0]  # ./指的是xpath方法的调用者
    content_list = each_div.xpath('./a[1]/div/span//text()')
    content_str = ''.join(content_list)
    print(author_str, content_str)

爬取指定页数
使用url模板:url_model = 'https://www.qiushibaike.com/text/page/%d/'

import requests
from lxml import etree

headers = {
    'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}

url_model = 'https://www.qiushibaike.com/text/page/%d/'
for each_page_num in range(1, 4):
    each_url = format(url_model % each_page_num)
    # main_url = 'https://www.qiushibaike.com/text/'
    each_text = requests.get(url=each_url, headers=headers).text

    tree_obj = etree.HTML(each_text)
    # 标签定位
    div_list = tree_obj.xpath('//div[@class="col1 old-style-col1"]/div')
    for each_div in div_list:
        # 局部数据解析,从每一个div中进一步进行数据解析
        author_str = each_div.xpath('./div[1]/a[2]/h2/text()')[0]  # ./指的是xpath方法的调用者
        content_list = each_div.xpath('./a[1]/div/span//text()')
        content_str = ''.join(content_list)
        print(author_str, content_str)

扩展:可以使用百度AI进行语音合成。
百度AI在线语音合成:https://ai.baidu.com/tech/speech/tts_online
技术文档:https://ai.baidu.com/ai-doc/SPEECH/Ik4nlz8l6

1.3.6 案例 爬取彼岸图网的4K动漫图片

彼岸图网的4K动漫图片:http://pic.netbian.com/4kdongman/

import requests
import os
from lxml import etree

dirname = './imgs'
if not os.path.exists(dirname):
    os.mkdir(dirname)

headers = {
    'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
url_model = 'http://pic.netbian.com/4kdongman/index_%d.html'
for each_page_num in range(1, 6):
    if each_page_num == 1:
        each_url = 'http://pic.netbian.com/4kdongman/'
    else:
        each_url = format(url_model % each_page_num)
    each_response_obj = requests.get(url=each_url, headers=headers)
    each_response_obj.encoding = 'gbk'
    each_page_text = each_response_obj.text
    
    tree_obj = etree.HTML(each_page_text)
    # tree_obj.xpath('//*[@id="main"]/div[3]/ul/li')  # Copy XPath
    li_list = tree_obj.xpath('/html/body/div[2]/div/div[3]/ul/li')  # Copy full XPath
    for each_li in li_list:
        each_title_str = each_li.xpath('./a/img/@alt')[0]
        each_img_name = '{filename}.jpg'.format(filename=each_title_str)
        each_img_src = each_li.xpath('./a/img/@src')[0]
        each_img_url = 'http://pic.netbian.com/{each_img_src}'.format(each_img_src=each_img_src)
        each_img_data = requests.get(url=each_img_url, headers=headers).content
        each_img_path = '{dirname}/{each_img_name}'.format(dirname=dirname, each_img_name=each_img_name)
        
        with open(each_img_path, 'wb') as fp:
            fp.write(each_img_data)
        print('{each_img_name} 已下载。'.format(each_img_name=each_img_name))
1.3.7 与bs4的对比

如果需要解析出带有标签名的文本,需要使用bs4。

2 反爬机制与反反爬策略

2.1 robots协议

robots协议,也称为robots.txt,是存放于网站根目录下的ASCII编码格式的文本文件,用于告诉网络搜索引擎的漫游器本网站中的哪些内容不可以被漫游器获取,哪些内容是可以被漫游器获取的。
robots协议并不是一个规范,而只是约定俗成的,所以并不能保证网站的隐私。

举例:

https://www.bilibili.com/robots.txt
User-agent: *
Disallow: /include/
Disallow: /mylist/
Disallow: /member/
Disallow: /images/
Disallow: /ass/
Disallow: /getapi
Disallow: /search
Disallow: /account
Disallow: /badlist.html
Disallow: /m/
https://www.taobao.com/robots.txt
User-agent: Baiduspider
Disallow: /

User-agent: baiduspider
Disallow: /

2.2 cookie反爬

案例:爬取雪球网中的资讯数据。
雪球网:https://xueqiu.com/

import requests

headers = {
    'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
target_url = 'https://xueqiu.com/statuses/hot/listV2.json'
params = {
    'since_id': '-1',
    'max_id': '74012',
    'size': '15', 
}
response_obj = requests.get(url=target_url, headers=headers, params=params)
print(response_obj.json())  
'''
{'error_description': '遇到错误,请刷新页面或者重新登录帐号后再试', 'error_uri': '/statuses/hot/listV2.json', 'error_data': None, 'error_code': '400016'}
'''

结果:爬取失败
解决方案:在headers中添加Cookie即可成功爬取数据。

在爬虫中处理Cookie的方式:
方式1:手动添加Cookie
利用抓包工具获取浏览器请求时携带的Cookie,将Cookie添加到headers字典中。
手动处理Cookie局限性:写入headers字典中的Cookie存在有效时长,会过期失效。

方式2:使用Session机制自动处理Cookie
获取一个Session对象,基于该Session对象进行请求发送。

在基于Session机制的请求响应过程中,如果服务器端产生了Cookie,会将Cookie自动保存到Session对象中。
如果Cookie被保存到Session对象中,则可以再次使用该Session对象发送请求,此次请求会自动携带Cookie。

import requests

headers = {
    'User-Agent': 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}

# 创建Session对象。
session_obj = requests.Session()
# 使用Session对象向首页发送请求。
# 服务端产生Cookie,自动将Cookie保存于Session对象中。
main_url = 'https://xueqiu.com/'
session_obj.get(url=main_url, headers=headers)

target_url = 'https://xueqiu.com/statuses/hot/listV2.json'
params = {
    'since_id': '-1',
    'max_id': '74012',
    'size': '15', 
}
# 使用Session对象再次发送请求,此次请求中自动携带Cookie。
response_obj = session_obj.get(url=target_url, headers=headers, params=params)
print(response_obj.json())

2.3 代理反爬

代理指的是代理服务器,用于对请求或响应进行转发或拦截。

在反爬中使用代理的目的
如果使用爬虫在段时间内对一个网站发起一个高频请求,该网站会检测出这个异常的现象,并会获取异常请求ip,将ip加入到黑名单中,则该ip在近期无法再次对该网站进行网络访问。
如果本机ip被对方服务器加入到黑名单中,可以使用代理服务器进行请求转发,对方服务器获取的请求ip是代理服务器的ip,而不是本机的ip。

代理的匿名度
透明:对方服务器知道请求方使用了代理,也知道请求方的真实ip;
匿名:对方服务器知道请求方使用了代理,但是不知道请求方的真实ip;
高匿:对方服务器不知道请求方使用了代理,也不知道请求方的真实ip。

代理的类型
http:只支持转发http协议的请求;
https:只支持转发https协议的请求;
兼容http和https。

获取对方服务器收到的请求ip
未使用代理

target_url = 'https://www.sogou.com/web?query=ip'
page_text = requests.get(url=target_url, headers=headers).text
tree_obj = etree.HTML(page_text)
tree_obj.xpath('//*[@id="ipsearchresult"]//text()')

使用代理

target_url = 'https://www.sogou.com/web?query=ip'
page_text = requests.get(
	url=target_url, 
	headers=headers, 
	proxies={'https': '代理服务器ip:代理服务器port'}
).text
tree_obj = etree.HTML(page_text)
tree_obj.xpath('//*[@id="ipsearchresult"]//text()')

获取代理
全网代理IP(免费):http://www.goubanjia.com/
智连HTTP(付费):http://http.zhiliandaili.cn/

构建代理池
提取智连HTTP提供的代理服务器的ip + port的方法

import random

proxy_list = []  # 代理池
# url是在智连HTTP购买代理并提取后生成的API链接
url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=51&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2'
page_text = requests.get(url, headers=headers).text
tree_obj = etree.HTML(page_text)
data_list = tree_obj.xpath('//body//text()')
for each_data in data_list:
    https_dic = {}
    https_dic['https'] = each_data
    proxy_list.append(https_dic)

使用代理池

url_model = 'http://www.521609.com/daxuemeinv/list8%d.html'
all_data_list = []
for each_page_num in range(1, 20):
    url = format(url_model % each_page_num)
    page_text = requests.get(
    	url=url, 
    	headers=headers, 
    	proxies=random.choice(proxy_list)  # proxy_list为上面构建的代理池
    ).text
    tree_obj = etree.HTML(page_text)
    li_list = tree_obj.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
    for each_li in li_list:
        detail_url = 'http://www.521609.com' + each_li.xpath('./a[1]/@href')[0]
        page_detail_text = requests.get(
        	url=detail_url, 
        	headers=headers,
        	proxies=random.choice(proxy_list)
		).text
        all_data_list.append(page_detail_text)
        
print(len(all_data_list))

你可能感兴趣的:(爬虫技术,xpath,python,Beautiful,Soup)