网络爬虫
概述
概述
网络爬虫是一种按照一定规则,自动地抓取万维网信息的程序或者脚本
网页中有很多信息和数据,要想从网页中抓取信息和数据,并保存到本地,这就是网络爬虫
网页一般是由HTML语言编写,要想从网页中抓取内容,其实就是在HTML中,找到相应的内容进行提取
网页
大部分的网页由三部分组成
HTML -- 超文本标记语言
整个网页的结构,类似于人的骨架,定义了你的眼睛、鼻子为位置在哪里
CSS -- 层叠样式表
CSS表示样式,定义了外观
JavaScript -- 脚本语言
JavaScript表示功能,网页中的交互内容,交互特效都包含在其中
HTML网页结构
网络请求
当用户浏览器向目标服务器发送请求的过程,称之为网络请求
常见的网络请求方式主要分为以下8种:
请求方式 | 描述 |
---|---|
GET | 发送请求来获得服务器上的资源,但请求的内容会以明文的方式显示在URL栏中 |
POST | 向服务器提交资源让服务器处理 |
HEAD | 主要用来检查资源或超链接的有效性或是否可以可达、检查网页是否被篡改或更新 |
PUT | 向指定资源位置中上传其更新内容 |
DELETE | 请求服务器删除某资源 |
CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器 |
OPTIONS | 允许客户端查看服务器的性能 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断 |
请求头常见参数
User-Agent
浏览器的名称
向服务器发送请求时,服务器通过该参数了解请求是从何种浏览器发出,以便返回正确的信息
referer:表明用户是从哪个网站跳转而来
cookie:HTTP协议是无状态的,cookie用于标识用户的身份信息
常见响应状态码
1xx -- 消息响应
表示临时响应并需要请求者继续执行操作的状态码
100(继续) 请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分
101(切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换
2xx -- 成功响应
表示成功处理了请求的状态码
200(成功) 服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。如果是对您的 robots.txt 文件显示此状态码,则表示 Googlebot 已成功检索到该文件
201(已创建) 请求成功并且服务器创建了新的资源
202(已接受) 服务器已接受请求,但尚未处理
203(非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源
204(无内容) 服务器成功处理了请求,但没有返回任何内容
205(重置内容) 服务器成功处理了请求,但没有返回任何内容。与 204 响应不同,此响应要求请求者重置文档视图(例如,清除表单内容以输入新内容)
206(部分内容) 服务器成功处理了部分 GET 请求
3xx -- 重定向响应
要完成请求,需要进一步操作。通常,这些状态码用来重定向
300(多种选择) 针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择
301(永久移动) 请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置
4xx -- 客户端错误
这些状态码表示请求可能出错,妨碍了服务器的处理
400(错误请求) 服务器不理解请求的语法
401(未授权) 请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应
403(禁止) 服务器拒绝请求
404(未找到) 服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码
5xx -- 服务器端错误
这些状态码表示服务器在处理请求时发生内部错误
500(服务器内部错误) 服务器遇到错误,无法完成请求
501(尚未实施) 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码
502(错误网关) 服务器作为网关或代理,从上游服务器收到无效响应
503(服务不可用) 服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态
504(网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求
505(HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本
URL
概述
URL是uniform Resource Locator的缩写,中文全称:统一资源定位符
每一个信息资源在网上都有唯一的一个地址,这就是URL
URL的组成
格式:
协议://用户名:密码@子域名.域名.顶级域名:端口号/目录/文件名.文件后缀?参数=值#锚部分
实例:
http://www.godsince.com:8080/news/index.asp?boardID=5&ID=24618&page=1#r_70732412
协议:模式/协议(scheme),在Internet中可使用多种协议,如HTTP,FTP等。在”HTTP”后面的“//”为分隔符
域名:也可使用IP地址作为域名使用。
端口:不是一个URL必须的部分,如果省略端口部分,将采用默认端口。
虚拟目录:从域名后的第一个“/”开始到最后一个“/”为止。虚拟目录不是一个URL必须的部分。
文件名:从域名后的最后一个“/”至“?”(或“#”或至结束)为止,是文件名部分。件名部分不是一个URL必须的部分,如果省略该部分,则使用默认的文件名。
参数:从“?”开始到“#”(或至结束)为止之间的部分为参数部分,又称搜索部分、查询部分。参数间用 “&”作为分隔符。
锚:或称片段(fragment),HTTP请求不包括锚部分,从“#”开始到最后,都是锚部分。
本例中的锚部分是 “r_70732423“。锚部分不是一个URL必须的部分。
锚点作用:打开用户页面时滚动到该锚点位置
三种提取数据方法
从HTML页面中提取所需的信息,一般有三种方法:Xpath语法、bs4库和正则表达式
三种方法对比
网络请求
网络爬虫基本流程
1、得到HTML页面,即发出网络请求
2、按照一定的规则进行数据的提取
3、数据的存储
HTTP请求
模拟http请求的过程是极其复杂的,用户需要向DNS发送一个请求以获取IP,再将获取后的IP发送给服务器,服务器返回HTML。
其实这个过程可以python代为完成,一般来讲,python有两个基础库来完成模拟请求:urllib 与 requests
注意:requests库并不是内置库,需要额外安装【安装命令:pip install requests】
示例
代码部分:
"""
@time:2022-03-30
@desc:
使用python实现豆瓣账号模拟登陆功能
"""
import requests
# 登陆的URL
url = "https://accounts.douban.com/passport/login"
# 伪装user-agent
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.60 Safari/537.36 "
}
# 账号信息
data = {
"name": "1666197054@qq.com",
"password": "douban.com459.."
}
# 模拟登陆
session = requests.session()
session.post(url, data, headers)
response = session.get(url="https://movie.douban.com",headers=headers)
status_code = response.status_code
print(status_code)
结果输出:
XPath
概述
XPath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历
简单来讲,我们要提取的数据为超文本数据,想要获取超文本数据里面的内容,就要按照一定规则来进行数据的获取,这种规则叫做Xpath语法
XPath用于在HTML文档中通过元素【HTML标签】和属性【HTML标签的属性】进行数据的定位
语法
1.节点选取
表达式 | 描述 | 示例 | 说明 |
---|---|---|---|
nodename | 选取此节点的所有子节点 | div | 选取div标签下的所有标签 |
// | 从全局节点中选择节点,任意位置均可 | //div | 选取整个HTML页面中的所有div标签 |
/ | 选取某个节点下的节点 | //head/title | 选取head标签下的的title标签 |
@ | 选取带某个属性的节点 | //div[@id] | 选取带有id属性的div标签 |
. | 当前节点下 | ./span | 选取当前节点下的span标签 |
2.谓语
表达式 | 描述 |
---|---|
//head/meta[1]//head/meta[k] | 选取所有head标签下的第一个meta标签选取所有head标签下的第k个meta标签 |
//head/meta[last()] | 选取所有head标签下的最后一个meta标签 |
//head/meta[position() < 3] | 选取所有head标签下的前两个meta标签 |
//div[@id] | 选取所有带有id属性的div标签 |
//div[@id='u1'] | 选取所有拥有id='u1' 属性的div标签 |
3.通配符
通配符 | 描述 | 示例 | 说明 |
---|---|---|---|
* | 匹配任意节点 | //div[@id='u1']/* | 选取所有拥有id='u1'属性的div标签下的所有节点 |
@* | 匹配节点中的任何属性 | //meta[@*] | 选取所有拥有属性的meta标签 |
4.选取多个路径
符号 | 描述 | 示例 | 说明 | ||
---|---|---|---|---|---|
\ | 选择多个路径 | //meta \ | //title | 选择所有的meta和title标签 |
lxml库
工作流程
发送网络请求 --> 获取HTML --> Xpath语法 --> 获取数据
注意:XPath语法无法直接作用于字符串进行数据提取
网页中的HTML可以用XPath语法获取数据,但无法在字符串中提取
lxml作用
将html字符串进行解析,供xpath语法进行数据提取
示例
简单取值
"""
@time:2022-03-31
@desc:
"""
from lxml import etree
# 读取txt文档
txt = \
"""
<tr class = "hosts">
<td class = '1', id = 'even' >host1</td>
<td class = '2', id = 'even' >host2</td>
<td class = '3' >host3</td>
<td class = '4' >hos4</td>
<td class = '5' >host5</td>
<td class = '6' >爬虫</td>
</tr>
"""
# 将txt字符串转换为html
html = etree.HTML(txt)
# 先对其进行编码再进行解码,就变成了字符串
# result = etree.tostring(html,encoding='utf8').decode('utf8')
# 1.获取所有的td标签
tds = html.xpath("//td")
for td in tds:
t = etree.tostring(td, encoding='utf8').decode('utf8')
# print(t)
# 2.获取第一个td标签
td1 = html.xpath('//td[1]')[0]
# print(etree.tostring(td1, encoding='utf8').decode('utf8'))
# 3.获取最后一个td标签
td_last = html.xpath('//td[last()]')[0]
# print(etree.tostring(td_last,encoding='utf8').decode('utf8'))
# 4.获取所有id='even'属性的标签
td_id = html.xpath("//td[@id='even']")
for id in td_id:
evens = etree.tostring(id, encoding="utf8").decode("utf8")
# print(evens)
# 5.获取标签中class属性值
class_value = html.xpath("//@class")
# print(class_value)
获取豆瓣TOP50 影评
"""
@time:2022-03-31
@desc:
爬取豆瓣TOP50影评
"""
import requests
from lxml import etree
# 获取影评信息
def info(url):
response = requests.get(url, headers=headers)
content = response.content.decode('utf-8')
html = etree.HTML(content)
# 电影名
move = html.xpath('//div[@class="subject-title"]/a/text()')[0][2:]
# 评论者
commentator = html.xpath('//header[@class="main-hd"]//span/text()')[0]
# 评分
scores = html.xpath('//span/@title')
if scores:
score = scores[0]
else:
score = "无评分"
# 评论
comment = html.xpath('//div[@id="link-report"]//p/text()')[0]
move_info = {
"电影名:": move,
"评论者:": commentator,
"评分:": score,
"评论:": comment
}
moves.append(move_info)
# 获取所有影评url链接
def urls():
for i in range(5):
url = "https://movie.douban.com/review/best/?start={}".format(i * 20)
response = requests.get(url, headers=headers)
content = response.content.decode('utf-8')
html = etree.HTML(content)
move_url = html.xpath('//h2/a/@href')
for url in move_url:
move_urls.append(url)
if __name__ == '__main__':
move_urls = []
moves = []
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.60 Safari/537.36 "
}
urls()
for url in move_urls:
info(url)
print("当前共{}条影评".format(len(moves)))
for move in moves:
for key,value in move.items():
print(key + ":" + value)
print("*" * 100)
微博热搜
"""
@time:2022-03-31
@desc:
爬取微博热搜榜
"""
import requests
from lxml import etree
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.60 Safari/537.36 "
}
url = "https://s.weibo.com/top/summary"
response = requests.get(url, headers=headers)
content = response.content.decode('utf-8')
html = etree.HTML(content)
titles = html.xpath('//td[@class="td-02"]/a/text()')
for title in titles:
print(title)
百度热搜
"""
@time:2022-03-31
@desc:
爬取百度热搜榜单
"""
import requests
from lxml import etree
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.60 Safari/537.36 "
}
url = "https://top.baidu.com/board?tab=realtime"
response = requests.get(url, headers=headers)
content = response.content.decode('utf-8')
html = etree.HTML(content)
title = html.xpath('//div[@class="category-wrap_iQLoo horizontal_1eKyQ"]//div[@class="c-single-text-ellipsis"]//text()')
index = html.xpath('//div[@class="category-wrap_iQLoo horizontal_1eKyQ"]//div[@class="hot-index_1Bl1a"]//text()')
for i in range(len(index)):
print("{},热搜指数:{}".format(title[i][2:],index[i][2:]))
结果输出:
BeautifulSoup
概述
和lxml一样, BeautifulSoup 也是一个HTML/XML的解析器,主要功能为解析和提取HTML/XML数据
简单来讲, BeautifulSoup只是一个从HTML字符串中提取数据的工具
BeautifulSoup与lxml的区别
lxml只会局部遍历,BeautifulSoup是基于HTML DOM树的,会加载整个文档,解析DOM树
因此所需的时间和内存开销都会很大,所以性能要低于lxml
练习基础语法
"""
@time:2022-03-31
@desc:
练习bs4语法
"""
text = \
"""
<ul class="ullist" padding="1" spacing="1">
<li>
<div id="top">
<span class="position" width="350">职位名称</span>
<span>职位类别</span>
<span>人数</span>
<span>地点</span>
<span>发布时间</span>
</div>
<div id="even">
<span class="l square">
<a target="_blank" href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218">python开发工程师</a>
</span>
<span class="position" width="350">技术类</span>
<span>2</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="odd">
<span class="l square">
<a target="_blank" href="position_detail.php?id=29938&keywords=python&tid=87&lid=2218">python后端</a>
</span>
<span>技术类</span>
<span>2</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="even">
<span class="l square">
<a target="_blank" href="position_detail.php?id=31236&keywords=python&tid=87&lid=2218">高级Python开发工程师</a>
</span>
<span>技术类</span>
<span>2</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="odd">
<span class="l square">
<a target="_blank" href="position_detail.php?id=31235&keywords=python&tid=87&lid=2218">python架构师</a>
</span>
<span>技术类</span>
<span>1</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="even">
<span class="l square">
<a target="_blank" href="position_detail.php?id=34531&keywords=python&tid=87&lid=2218">Python数据开发工程师</a>
</span>
<span>技术类</span>
<span>1</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="odd">
<span class="l square">
<a target="_blank" href="position_detail.php?id=34532&keywords=python&tid=87&lid=2218">高级图像算法研发工程师</a>
</span>
<span>技术类</span>
<span>1</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="even">
<span class="l square">
<a target="_blank" href="position_detail.php?id=31648&keywords=python&tid=87&lid=2218">高级AI开发工程师</a>
</span>
<span>技术类</span>
<span>4</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="odd">
<span class="l square">
<a target="_blank" href="position_detail.php?id=32218&keywords=python&tid=87&lid=2218">后台开发工程师</a>
</span>
<span>技术类</span>
<span>1</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="even">
<span class="l square">
<a target="_blank" href="position_detail.php?id=32217&keywords=python&tid=87&lid=2218">Python开发(自动化运维方向)</a>
</span>
<span>技术类</span>
<span>1</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
<div id="odd">
<span class="l square">
<a target="_blank" href="position_detail.php?id=34511&keywords=python&tid=87&lid=2218">Python数据挖掘讲师 </a>
</span>
<span>技术类</span>
<span>1</span>
<span>上海</span>
<span>2018-10-23</span>
</div>
</li>
</ul>
"""
# 导入模块
from bs4 import BeautifulSoup
# 实例化BeautifulSoup对象
# BeautifulSoup(对象,解释器类型)
soup = BeautifulSoup(text,'lxml')
"""
1.获取所有的div标签,find_all()
返回的同样是列表
divs = soup.find_all('div')
print(type(divs))
for div in divs:
print(div, '\n', '*'*50, end='\n')
"""
"""
2.获取第2个div标签
div_1 = soup.find_all('div')[1]
print(div_1)
"""
"""
3.获取从第二个到第十个的div标签
div_2_10 = soup.find_all('div')[1:10]
for div in div_2_10:
print(div, '\n', '*'*50, end='\n')
"""
"""
4.获取拥有指定属性的标签(id=even的div标签)
# 方法1
divs_id = soup.find_all("div", id="even")
for div in divs_id:
print(div, '\n', '*'*50, end='\n')
# 方法2,以字典形式
divs_id = soup.find_all('div', attrs={"id": "even"})
for div in divs_id:
print(div, '\n', '*'*50, end='\n')
"""
"""
5.获取拥有多个属性的标签(class=position,width=350的div标签)
当属性名与关键词冲突时,需要在属性名后加_
# 方法1:
spans = soup.find_all('span', class_='position', width='350')
for div in spans:
print(div, '\n', '*'*50, end='\n')
# 方法2
spans = soup.find_all('span', attrs={"class": "position", "width": "350"})
for div in spans:
print(div, '\n', '*'*50, end='\n')
"""
"""
6.获取属性值(a标签中的href值)
# 方法1,,通过下标方式提取
a_s = soup.find_all('a')
for a in a_s:
href = a['href']
print(href)
# 方法2,利用attr参数提取
a_s = soup.find_all('a')
for a in a_s:
href = a.attrs['href']
print(href)
"""
"""
7.获取所有的职位信息
# 方法1
infos = []
divs = soup.find_all("div")[1:]
for div in divs:
# .string 获取标签下的的全部文本信息
# 职业
profession = div.find_all('a')[0].string
# 类别
position = div.find_all('span')[1].string
# 人数
numbers = div.find_all('span')[2].string
# 地区
area = div.find_all('span')[3].string
# 时间
time = div.find_all('span')[4].string
positions = {
"职业": profession,
"类别": position,
"人数": numbers,
"地区": area,
"时间":time
}
infos.append(positions)
for info in infos:
for key,value in list(info.items()):
print(key + ":" + value)
print("*" * 30)
# 方法2
divs = soup.find_all('div')[1:]
for div in divs:
# stripped_strings 去除掉换行符,空格等无意义字符
# .string获取标签下的全部文本信息
infos = list(div.stripped_strings)
print(infos)
"""
示例
工作流程
- 导入模块
- 定义url和请求头参数
- requests发送HTML请求,获取HTML字符串
- 实例化BeautifulSoup对象(中介)
- 获取数据
- 存储数据
微博热搜
"""
@time:2022-03-31
@desc:
爬取新浪微博搜
"""
from bs4 import BeautifulSoup
import requests
sinas = []
url = "https://s.weibo.com/top/summary?display=0&retcode=6102"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.60 Safari/537.36 "
}
# 发起网络请求
response = requests.get(url, headers=headers)
content = response.content.decode('utf-8')
# 实例化beautifulsoup对象
soup = BeautifulSoup(content, 'lxml')
tds = soup.find_all('td', attrs={"class": "td-02"})
for td in tds:
event = td.find_all('a')[0].string
hot = td.find_all('span')[0].string
sina = {
"title:": event,
"hot:": hot
}
sinas.append(sina)
print(sinas)
百度热搜
"""
@time:2022-03-31
@desc:
百度热搜
"""
from bs4 import BeautifulSoup
import requests
url = "https://top.baidu.com/board?tab=realtime"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/100.0.4896.60 Safari/537.36 "
}
# 发起网络请求
response = requests.get(url,headers=headers)
content = response.content.decode('utf-8')
# 实例化beautifulsoup对象
soup = BeautifulSoup(content, 'lxml')
# 获取数据
divs = soup.find_all('div', attrs={"class": "c-single-text-ellipsis"})
hots = soup.find_all('div', class_="hot-index_1Bl1a")
# 显示数据
for i in range(len(list(divs))):
print("{},热度:{}".format(divs[i].string[2:],hots[i].string[2:]))
结果输出:
本文系作者 @小白学安全 原创发布在 xbxaq.com 站点,未经许可,禁止转载!
评论