相关库的使用
Requests
Requests库是Python的第三方库,是目前公认的获取网页最好的库,特点有简单,代码简洁,甚至一行代码就能爬取到网页。
我们可以使用requests发送请求:
import requests
# 发送请求
url="http://www.baidu.com"
r=requests.get(url)
# 检查连接状态, 如果是200就是正常
print(r.status_code)
# 查看内容
print(r.text)
# 查看返回的编码方式
print(r.encoding)
也可以传递url参数:
import requests
# 传递参数: 比如http://xxx?aa=bb&cc=dd, 通常网页有这种形式http://aaa.com?pageId=1&type=content
params={'k1':'v1','k2':'v2'}
r=requests.get('http://httpbin.org/get',params)
print(r.url) # http://httpbin.org/get?k1=v1&k2=v2
params={'k1':'v1','k2':[1,2,3]}
r=requests.get('http://httpbin.org/get',params)
print(r.url) # http://httpbin.org/get?k1=v1&k2=1&k2=2&k2=3
上面例子中,使用到了http://httpbin.org/get
,httpbin
主要用于测试 HTTP 库。可以向其发送请求,然后会按照指定的规则将请求返回。可以支持返回一个HTML文件或一个XML文件或一个图片文件,是请求调试的便捷工具。
传递参数的意义在于我们可以直接用get方法实现网页翻页,而不需要像selenium那样通过拼接url字符串翻页。
下一步,我们可以用requests读取响应内容,包括文本,二进制文件,Json文件。
我们从网上读取图像并保存:
import requests
from io import BytesIO
from PIL import Image
# 读取二进制数据需要用到BytesIO(r.content), 比如从新浪获取一张图像
r=requests.get('http://n.sinaimg.cn/sports/transform/267/w640h427/20191010/6795-ifrwayx3508589.jpg')
image=Image.open(BytesIO(r.content))
image.save("durant.jpg")
通常,r.content
保存了二进制数据,r.text
是已经处理为文本的数据
我们读取json文件:
# json数据处理
r=requests.get('https://github.com/timeline.json')
print(type(r.json)) # <class 'method'>
print(r.json) # <bound method Response.json of <Response [410]>>
print(r.text)
"""
这也是出现410的原因, http 410错误状态码表明资源(图片、css、js和所有其他文件)永久失效
{"message":"Hello there, wayfaring stranger.
If you’re reading this then you probably didn’t see our blog post a couple of years back announcing that this API would go away:
http://git.io/17AROg Fear not,
you should be able to get what you need from the shiny new Events API instead.
","documentation_url":"https://docs.github.com/v3/activity/events/#list-public-events"}
"""
我们读取原始数据(处理流数据),以前面的图像获取为例:
# 指定stream, 定义为流数据格式, 即原始数据
r=requests.get('http://n.sinaimg.cn/sports/transform/267/w640h427/20191010/6795-ifrwayx3508589.jpg',stream=True)
# w表示write, b表示二进制, +表示将原来的文件内容删除
with open('durant2.jpg','wb+') as f:
for chunk in r.iter_content(1024):
f.write(chunk)
我们使用requests.post
提交表单:
# 提交表单, 比如用于提交密码
# 可以POST一个表单, 也可以POST一个值
# 提交字典格式的数据就认为是表单
form={'username':'user','password':'pass'}
r=requests.post('http://httpbin.org/post',data=form)
print(r.text)
"""
表单保存在form字段
{
"args": {},
"data": "",
"files": {},
"form": {
"password": "pass",
"username": "user"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Content-Length": "27",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.27.1",
"X-Amzn-Trace-Id": "Root=1-61f28112-78c633bc43bd7518707e0372"
},
"json": null,
"origin": "154.31.114.253",
"url": "http://httpbin.org/post"
}
"""
# 传入一个纯文本
import json
form={'username':'user','password':'pass'}
r=requests.post('http://httpbin.org/post',data=json.dumps(form))
print(r.text)
"""
表单保存在data字段
{
"args": {},
"data": "{\"username\": \"user\", \"password\": \"pass\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Content-Length": "40",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.27.1",
"X-Amzn-Trace-Id": "Root=1-61f2824d-77dc7213045983f8629de9b7"
},
"json": {
"password": "pass",
"username": "user"
},
"origin": "154.31.114.253",
"url": "http://httpbin.org/post"
}
"""
注意GET请求和POST请求的区别:
- GET:向特定资源发出请求(用于获取数据)
- POST:向指定资源提交数据进行处理请求(用于登录,构造请求)
Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。我们可以获取cookies:
# 获取cookie
url="http://www.baidu.com"
r=requests.get(url)
cookies=r.cookies
# cookie保存在字典里
for k,v in cookies.get_dict().items():
print(k,v) # BDORZ 27315
# 传入参数cookies
# 比如cookies可以存储一些历史用户登录信息, 就不用每次都post
cookies={'c1':'v1','c2':'v2'}
r=requests.get('http://httpbin.org/cookies',cookies=cookies)
print(r.text)
"""
{
"cookies": {
"c1": "v1",
"c2": "v2"
}
}
"""
重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置,比如http转到https。查看重定向可以帮助我们了解网页的跳转。我们可以使用requests检测重定向:
r=requests.head('http://gitee.com',allow_redirects=True)
print(r.url) # https://gitee.com/
print(r.status_code) # 200
print(r.history) # [<Response [301]>]
# 301 状态码表明目标资源被永久的移动到了一个新的 URI,任何未来对这个资源的引用都应该使用新的 URI
我们有时候需要使用代理。
网络代理实际上就是利用代理服务器代替客户端访问网站或者网页。代理服务器是用户和互联网之间的网关。它被称为“中介”,因为它在用户和在线访问的网页之间运行。代理服务器为我们的计算机提供了一个安全层。代理可以设置为网络过滤器或防火墙,保护计算机免受恶意软件等互联网威胁;也可以设置为访问外网某些资源的中介,比如翻墙(代理服务器可以翻墙,则本机就能翻墙)。代理服务器工作流程如下:
- 使用代理服务器,首先需要在的计算机、设备或网络中设置代理。代理服务器有自己的IP地址,所以它充当了计算机和互联网的中间人。我们的电脑知道这个地址,当我们在互联网上发送请求时,请求会先发送到代理服务器,然后代理从网络服务器获取响应并将页面中的数据转发到我们电脑的浏览器。
关于代理,不同的协议也存在不同的代理。
requests使用代理的示例如下:
proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)
Beautiful Soup
Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库,简单来说,它能将HTML的标签文件解析成树形结构,然后方便地获取到指定标签的对应属性。
通过Beautiful Soup库,我们可以将指定的class或id值作为参数,来直接获取到对应标签的相关数据,这样的处理方式简洁明了。
安装为:
conda install beautifulsoup4
比如有一个排版很差的test.html:
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
我们用BS4来美化排版:
from bs4 import BeautifulSoup
# features指明解析器
soup=BeautifulSoup(open("test.html"),features="html.parser")
# 美化html格式
print(soup.prettify())
得到:
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
<body>
<p class="title" name="dromouse">
<b>
The Dormouse's story
</b>
</p>
<p class="story">
Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">
<!-- Elsie -->
</a>
,
<a class="sister" href="http://example.com/lacie" id="link2">
Lacie
</a>
and
<a class="sister" href="http://example.com/tillie" id="link3">
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p class="story">
...
</p>
</body>
</html>
我们可以简单地获取指定元素:
# 获取title元素
print(soup.title) # <title>The Dormouse's story</title>
print(type(soup.title)) # <class 'bs4.element.Tag'>
print(soup.title.name) # title
# 去除标签符号, 保留内容的字符串
print(soup.title.string) # The Dormouse's story
# 标签的中间内容有时候是字符串, 有时候是注释, 可以通过type分辨
print(type(soup.title.string)) # <class 'bs4.element.NavigableString'>
print(type(soup.a.string)) # <class 'bs4.element.Comment'>
我们可以遍历某元素下的子元素:
# 遍历文档中body下的子元素, 只包括第一层子元素, 不考虑更深的子元素
for items in soup.body.contents:
print(items)
"""
得到3个子元素p:
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
我们可以使用CSS选择器:
# 使用css选择器定位元素, 符合选择条件的结果保存到列表中
# 类选择器
print(soup.select('.sister'))
"""
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
"""
# id选择器
print(soup.select('#link1')) # [<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
# 子元素选择器
print(soup.select('head > title')) # [<title>The Dormouse's story</title>]
可以看出,在元素查找方面,BS4比selenium更简单,更直观。
HTMLParser
HtmlParser是一个Python内置基类,通常我们需要继承该类以达到解析出需要的数据的目的。
常用方法:
handle_starttag(tag, attrs)
:处理开始标签,比如<div>
,attrs
获取到的是属性列表,属性以元组的方式展示;handle_endtag(tag)
:处理结束标签,比如</div>
;handle_startendtag(tag, attrs)
:处理自结束标签,如<img />, <br/>
;handle_data(data)
:处理数据,即标签之间的文本;handle_comment(data)
:处理注释,即<!-- -->
之间的文本;
实例如下:
from html.parser import HTMLParser
class MyParser(HTMLParser):
def handle_decl(self, decl):
HTMLParser.handle_decl(self, decl)
print('decl %s' % decl)
def handle_starttag(self, tag, attrs):
HTMLParser.handle_starttag(self, tag, attrs)
print('<' + tag + '>')
def handle_endtag(self, tag):
HTMLParser.handle_endtag(self, tag)
print('</' + tag + '>')
def handle_data(self, data):
HTMLParser.handle_data(self, data)
print('data %s' % data)
#<br/>
def handle_startendtag(self, tag, attrs):
HTMLParser.handle_startendtag(self, tag, attrs)
def handle_comment(self, data):
HTMLParser.handle_comment(self, data)
print('data %s' % data)
def close(self):
HTMLParser.close(self)
print('Close')
demo = MyParser()
demo.feed(open('test.html').read())
demo.close()
在实际使用中,我们只需要为不同场景下的元素,在对应的方法中实现处理过程,就能实现HTML的自定义功能解析。比如函数close(self)
下,我们定义了两个处理步骤:
- 第一个是来自基类的处理方法
HTMLParser.close(self)
; - 第二个是打印记录信息
print('Close')
;
BS4是基于DOM模型的,并且有一些过度封装,HTMLParser基于SAX模型,节省内存,更加高效,并且支持用户灵活定义处理HTML的过程。
数据库编程
我们使用简洁的sqlite演示数据库编程。
对于仅需本地操作且轻量级的数据库,使用SQLite比MySQL更方便。相比MySQL更加轻便,适合单机程序。
并且Python内置支持SQLite。
数据库编程的实例如下,数据库编程的要点在于SQL语句,我们只是用Python脚本执行SQL语句:
import sqlite3
# 创建数据库, 若没有则新建
conn = sqlite3.connect('./test.db')
# sql语句
create_sql = 'create table company(id int primary key not null, emp_name text not null);'
# 执行sql语句
conn.execute(create_sql)
insert_sql = 'insert into company values(?, ?)'
conn.execute(insert_sql, (100, 'Remote'))
conn.execute(insert_sql, (200, 'Image'))
cursors = conn.execute('select id, emp_name from company')
for row in cursors:
print(row[0], row[1])
"""
100 Remote
200 Image
"""
# 关闭链接
conn.close()
Requests登录(基于Cookie)
如果通过提交账号密码的表单去登录,当遇到各种复杂的验证码信息时,使用脚本处理会非常麻烦。所以我们最好使用Cookies登录。
我们先人为地登录,并记录cookie信息,再利用requests去请求,实现爬虫登录。
以登录豆瓣为例,首先人为注册账号,并登录,让浏览器记住登录后的状态,打开开发者工具,记录标头中的cookie信息(右键,复制值):
然后利用cookie参数去获取登录后的网页内容:
import requests
# 模拟浏览器
"""
User-Agent 即用户代理,简称“UA”,它是一个特殊字符串头
网站服务器通过识别 “UA”来确定用户所使用的操作系统版本、CPU 类型、浏览器版本等信息
而网站服务器则通过判断 UA 来给客户端发送不同的页面
"""
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36'}
# 填写cookie
cookies = {'cookie': '...'}
url = 'http://www.douban.com'
# GET请求登录后的页面
r = requests.get(url, cookies = cookies, headers = headers)
# 将内容写入本地html文档, 也可以写入txt, 或者做后续更复杂的处理
with open('douban.html', 'wb+') as f:
f.write(r.content)
获取豆瓣电影Top250
爬取豆瓣Top250电影的实例如下:
import requests
from lxml import etree
# 建立一个requests会话
# 会话对象能够跨请求保持某些参数, 它也会在同一个 Session 实例发出的所有请求之间保持 cookie
# 会话对象具有主要的 Requests API 的所有方法, 可以当成 Request去使用
s = requests.Session()
# 添加头信息, 模拟浏览器
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36'}
# 豆瓣的页数, 每隔25为1页
for id in range(0, 251, 25):
url = 'https://movie.douban.com/top250/?start=' + str(id) + '&filter='
r = s.get(url,headers=headers)
# 指定编码方式
r.encoding = 'utf-8'
# 找到根元素
root = etree.HTML(r.content)
# 使用XPath定位元素
items = root.xpath('//ol/li/div[@class="item"]')
print(len(items)) # 25, 因为每一页有25个item
# 遍历item元素
for item in items:
title = item.xpath('./div[@class="info"]//a/span[@class="title"]/text()')
name = title[0].encode('gb2312', 'ignore').decode('gb2312')
rating = item.xpath('.//div[@class="bd"]//span[@class="rating_num"]/text()')[0]
print(name, rating)
"""
25
肖申克的救赎 9.7
...
猜火车 8.6
"""
对于这个任务,我们可以尝试使用登录后的cookie,也可以不登录,重点在于如何定位我们要的元素,比如电影中文名以及评分。由于豆瓣的html是比较规则的,所以我们可以将html视为xml那样的规则文档,并用XPath去定位元素。