数据解析就是将一组数据中的局部数据进行提取,用于实现聚焦爬虫。
聚焦爬虫是建立在通用爬虫的基础上,从通用爬虫获取的整个源码数据中提取出指定的数据。
常用的数据解析方式包括:
正则匹配、bs4、XPath(常用)、PyQuery等。
方式1:BeautifulSoup(fp, 'lxml')
用于对本地存储的html文件进行数据解析;
方式2:BeautifulSoup(page_text, 'lxml')
用于对互联网上请求到的页面资源进行数据解析。
解析器一般选用lxml
。
标签定位
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')
取文本
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']
目标:从诗词名句网爬取《三国演义》
《三国演义》: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))
html标签是基于树状结构的。
方式1:etree.parse('filename')
用于对本地存储的数据进行解析。
方式2:etree.HTML('page_text')
用于对互联网上请求到的页面资源进行数据解析。
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")]')
取文本
/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']
糗事百科的段子: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
彼岸图网的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))
如果需要解析出带有标签名的文本,需要使用bs4。
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: /
案例:爬取雪球网中的资讯数据。
雪球网: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())
代理指的是代理服务器,用于对请求或响应进行转发或拦截。
在反爬中使用代理的目的
如果使用爬虫在段时间内对一个网站发起一个高频请求,该网站会检测出这个异常的现象,并会获取异常请求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))