青魔法-驭虫术(不定时更新)
青魔法Python-驭虫术
本文作者:魏泯
博客源地址:https://www.cnblogs.com/simple/
做有效率的魔法师
目录
- 概念、工具以及协议
- 使用requests模块开发爬虫程序
- rquests请求
- requests的其他参数
- response响应对象
- 处理cookie相关的请求
- Scrapy框架
- 第三方模块
- 微型ORM(对象关系映射)脚本【源代码】
概念、工具 以及 协议
爬虫
爬虫就是 模拟浏览器发送网络请求,获取响应,按照规则提取数据的程序。爬虫要的就是保存数据,只抓取不保存毫无用处。数据流向
- 呈现出来,用于网页服务,或应用服务
- 进行保存、数据分析
需要的软件与环境
- python环境
- Pycharm
- chrome浏览器
浏览器的请求
Url:需要进行解码或转码
爬虫和浏览器请求的URL地址有一定差别。Elements与Network中的源码会不一样。
HTTP与HTTPS
Http:超文本传输协议:以明文方式进行传输,效率更高。
Https:Http+SSL(安全套接字层):传输数据之前,将数据加密,之后解密,效率较低,但是更安全。
这两者都包含请求与响应部分。
请求
Http报文请求由三部分组成:请求行、请求头、请求体
- 请求头:
Status code:状态码
Conection: keep-alive 支持长连接
Cache-Control:max-age=0 缓存控制为零
User-Agent:浏览器(内核)身份标识
Upgrade-Insecure-Requests:支持将不安全请求转换的功能
Accept:表示我想接收什么数据,q=权重
Cookie:浏览器保存在用户本地的一些信息,包括用户密码,账号信息,网站校验信息(例如csrf跨站保护)。会被携带发送给服务器。Cookie可以被服务器用来判断客户端是否是爬虫。
Query-String-Parameters:请求数据(GET)
From-data:请求数据(POST)
可以在Chorme中模拟多种版本的机器
- 请求体
GET请求没有请求体,数据放在URL中
POST请求有请求体:携带数据,参数放置于请求体中,常用于登陆注册、传输大文本。
响应
- 响应头: set-Cookie——服务器给本地设置的cookie信息,服务器通过该字段设置cookie
- 响应体: URL地址对应的响应
- 抓包:就是按下F12,在Network中查看浏览器发送了什么请求的信息,这个过程就叫做抓包
使用requests模块开发爬虫程序
requests的方法
requests模块是一个第三方包:用于模拟http数据请求。
在接下来的内容里我们学习一下它的多种请求。
requests方式比urlib方式简单
50%的动态内容是json格式的数据。
requests模块的请求方法
- requests请求的get请求方式Http请求的get的请求方式:
使用这种方式得到一个响应对象
url = "http://www.baidu.com/"
response = requests.get(url=url)
Terminal:
<Response [200]>
- requests请求的post请求方式
使用这种方式得到一个响应对象
url = "http://www.baidu.com/"
data = {键:"值"}
response = requests.get(url=url,data=data)
Terminal:
<Response [200]>
请求头部信息:
- 伪造/重塑 请求头部信息:headers是一个Hash字典,以键值的方式存放头部信息。
例如:
headers={"键":"值,}
response = requests.get(url=url, headers=headers)
requests的其他参数
除了url和以外还有很多参数,比如timeout、代理IP等等requests请求的超时参数——timeout
配合异常捕获,在指定时间返回响应
# 在三秒内必须返回响应,否则则会报错
response = requests.get(url=url,headers=headers,timeout=3)
建立代理
- 建立代理
proxies = [{"http":"113.122.8.45:53128"},{"http":"114.122.8.45:53128"},{"http":"115.122.8.45:53128"}]
- 导入随机数模块
proxy = random.choice(proxies)
response的方法:
通过response
获取页面的响应内容,包括响应头、响应体。
这是我们所使用的公共代码:
import requests
url = 'https://www.cnblogs.com/Asterism-2012/'
response = requests.get(url=url)
获取源代码
- response.content
第一种情况,使用content方法获取源代码,但是这种方式是以byte的方式获取源代码,先看一下它是什么,看完这个我们稍后对它解码
print(response.content)
Terminal:
b'\r\n<!DOCTYPE html>\r\n<html lang="zh-cn">\r\n<head>\r\n<meta ......略
- response.content.code()
我们发现刚才打印的content内容是byte,为了便于观看,进行解码:
它会将content的内容解码为str
类型,也可以自行指定解码类型。
x = response.content.decode()
print(type(x))
print(x)
Terminal:
<class 'str'>
略......<p>by: 魏泯</p><!--PageEndHtml Block End--></body></html>
decode()自行指定content解码类型:
x= response.content.decode('utf-8')
print(type(x))
Terminal:
<class 'str'>
- response.text
使用text
方法获取到的源代码,是根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码。这种方法简单常用,适用于大部分的网站。当然既然是推测的代码,就也有出现错误的时候,这也没关系,到时候我们可以通过指定编码来解决这个问题。
print(response.text)
Terminal:
略......<p>by: 魏泯</p><!--PageEndHtml Block End--></body></html>
设置服务器的编码为本地的编码
但是有的时候即便进行了解码,也无法正常看到页面源码。造成这样的原因是本地编码与服务器编码不统一,所以我们要对响应回来的内容制定编码。- 第一种方式,自动获取:
response.encoding = response.apparent_encoding
- 第二种方式,手动抓包设置,假设以抓到的是gbk为例:
response.encoding=”gbk”
请求与响应的目标url地址
- 请求目标站点的Url:
有时候我们需要获取到目标站点的url,用于拼接新的url或者做保存、跳转等工作,所以就用到了response.request.url
这个方法
x = response.request.url
Terminal:
https://www.cnblogs.com/Asterism-2012/
- 实际发生响应的Url地址:
响应的url地址,有时候会因为跳转而和请求目标地址不一致,使用response.url
方法来获取它:
x = response.url
print(x)
Terminal:
https://www.cnblogs.com/Asterism-2012/
请求与响应的头部信息
- 查看请求头部信息:
有时候我们需要测试自己的代理IP池、Header池信息,就可以通过这个方法来动态获取头部信息进行查看
x = response.request.headers
print(x)
Terminal:
{'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
- 查看响应头部信息:
x = response.headers
print(x)
Terminal:
{'Date': 'Fri, 04 Jan 2019 03:53:18 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'Cache-Control': 'private, max-age=10', 'Expires': 'Fri, 04 Jan 2019 03:53:28 GMT', 'Last-Modified': 'Fri, 04 Jan 2019 03:53:18 GMT', 'X-UA-Compatible': 'IE=10', 'X-Frame-Options': 'SAMEORIGIN', 'Content-Encoding': 'gzip'}
处理cookie相关的请求
- DEMO:人人网
- 直接携带cookie,然后请求URL
- 直接放在headers中
headers = {"Referver":"URL","User-Agent":"浏览器信息","Cookie":"..."}
- cookie字典传给cookie参数,这个办法非常酷。Cookie参数只接收字典。【传说中的字典推导式】
# Cookie字符串 CookieStr = "anonymid=jm5lz4q0wv29sz; depovince=BJ; _r01_=1; JSESSIONID=abcpoYHrUXJZa6yQOiLxw; ick_login=4c5c0781-d37e-44c5-98a7-8e3fee81d222; ick=e74b83d4-c258-4de9-bd6c-3426f777fadd; XNESSESSIONID=949847ff1bf9; jebe_key=38dc9339-f175-472f-9288-02e4e7e4b59c%7Cd931901a60720bfd1a4f2f38c98639f3%7C1537147847212%7C1%7C1537147849534; wp_fold=0; jebecookies=054ec649-ffed-4989-beda-1be907785a33|||||; _de=BA25AEE63DF6B7A91E89E9A775AD2FBD; p=5929761e2707b622b09b23f92b3c04aa8; first_login_flag=1; ln_uact=15661592012; ln_hurl=http://head.xiaonei.com/photos/0/0/men_main.gif; t=aaf1d9103cf916e1d824260c832ceaf38; societyguester=aaf1d9103cf916e1d824260c832ceaf38; id=968054908; xnsid=1acd462c; ver=7.0; loginfrom=null"
# 这是一个字典生成式,生成正式使用的Cookie Cookie = {i.split("=")[0]:i.split("=")[1] for i in CookieStr.split(";")} response = requests.get(url=url,headers=headers,cookies=Cookie)
- 通过session模块获取cookie:(这应是最常用的方法)
- session = requests.session() 会话保持,实例session
- session.post(post_url,data,headers) 很多网页的接口可以在表单的action提交地址中看到,但有一些需要抓包才能看到
- response = session.get(url,headers)
- 使用session不仅可以处理相关登陆,而且可以应对相关反爬措施。
- 处理登陆相关的验证码可以找:打码兔
数据提取方法
JSON
- 数据交换格式,在Python的识别中其实是一个字符串
- 学习处理和操纵json数据,非常重要
- 导入标准库模块json
import json
- json.loads(JSON数据) Json字符串重载为Python可识别的字典(dict)类型
- 将Json字符串转化为Python容器类型(Dic,List)
Py_data = json.loads(response.text)`
- json.dumps(dict) 字典转换成json字符串
- 把Python数据格式转化成字符串
Js_data = json.dumps(Py_data)
- dumps参数;ensure_ascii=False 取消ASC编码
Js_data = json.dumps(Py_data,ensure_ascii=False)
- dumps参数;indent=2 以首行缩进两个空格 的形式打印或保存,美化效果
Js_data = json.dumps(Py_data,indent=2)
- 浏览器的手机模式
- 抓包:
- 过滤:可以通过过滤URL来大幅提高抓包效率
- Preview:中文不会被编码,会直接正常显示
- API的获取注意:动态数据无法正常显示,很可能是Referer导致的
Xpath与lxml
Xpath是一种提取数据的语言
- 当使用Xpath-helper工具 定位到任何一个节点,该节点都会得到一个名为xh-highlight的class属性做标识
- XPath-helper插件
- 解析网页内容:静态内容(源代码中有的)。
- ctrl+shift+x 呼出XPath-helper
- 按住shift移动光标,快速捕获响应的xpath
- 1.选择节点对象(标签)
/html/head/meta
:能够选中html下的head下的所有的meta标签
- 2.从任意节点开始选择
//li
:当前页面所有的li标签/html/head/link//li
:表示meta下的所有li标签
- 3.定位到具体元素 [@]
//div[@class="father"]//li
: 获取class名为father的div下的所有li标签- 可以使用单引号
- 在页面中,定位id往往比定位class更合适
- 4.获取节点内文本 text()
//a/text()
:获取a标签中直接裸露的文本内容a//text()
:获取a下的所有文本,包括子节点文本
- 5.选择节点属性内容
a/@href
:获取a标签中 href属性中的内容
xpath方法
- string()函数 提取多个子节点中的文本
# 获取词语解释
w_ex = html2.xpath('//*[@id="content"]/div[2]')[0].xpath("string(.)")
lxml模块
-
第三方模块,解析xml内容
-
etree.HTML方法,将响应的代码格式化,解析成标准的html格式。
-
@后面跟属性,如果放在方括号中,代表对所过滤的标签集合进行过滤。如果放在url后,则代表获取该属性的值
etree.HTML(response.text)
相对路径与绝对路径
- /是绝对路径,绝对路径的查找难度比较大,比较麻烦
- //是相对路径,一般情况下我们都使用相对路径进行查找
mango数据库或文件夹
- 保存爬取内容
- 在xpath中的下标切片,不支持负索引,而且是从一开始,而不是从零开始的。
- 把tbody去掉就好用了
JSON-View插件
- 更便于查看JSON数据,在原来的基础上,优化了JSON数据的显示格式
response
- text方法,响应的源代码,字符串类型;可以提取汉字。
- encoding编码
# 将响应信息编码改为网站制定编码
response.encoding = response.apparent_encoding
文件操作
- os模块,用于操作系统文件
- 判断文件夹是否存在
dir = "directory"
if not os.path.exsits(dir):
pass
- 创建文件夹
dir = "directory"
os.mkdir(dir)
- 打开文件与文件读写
with("file_name","w+",encoding="utf-8") as f:
f.write(content)
爬取动态内容与基础反爬
- 网页是响应式加载,而不是一次性全部自动加载的
- Grome浏览器F12:
- Network中可以查看请求信息
- Network-XHR 查看动态请求信息
- user-agent/eɪdʒənt/:显示请求浏览器的内核
# 获取返回响应信息头部,这个不是很重要
for v,j in response.headers.items():
print(v,':',j)
# 获取请求信息头部
for v,j response.request.headers.items():
print(v,':',j)
- 伪造请求信息浏览器内核
headers = {
'User-Agent':'伪造的浏览器内核信息'
}
response = requests.get(url=url,headers=headers)
- Referer溯源伪造
有些网址的反爬策略是:某些网址的特定网页会识别客户端的refer信息,这些信息包含了客户端的请求记录。它的具体意思是,你必须经由访问了我的主页,跳转到我的子页面,而不是从URL直接过来。你先开我家门,才能到我的卧室,而不是从窗户进来。否则就认为你是不合法的。
headers = {
'Referer':'伪造的 上一级URL'
}
response = requests.get(url=url,headers=headers)
- 在XHR中可以找到动态数据,以及动态数据的请求接口。
- 将json数据格式,格式化
py_date = json.loads(response.text())
IP代理 requests.proxies
- 相关则是随机数模块 random
# 代理IP列表
proxies = [{"http":“133.12.45.12:8564”},{"协议":"IP:端口号"}]
# random随机选择列表中的一个元素生成代理
proxy = random。choice(proxies)
response = requests.get(proxies=proxy)
MongoDB 数据库
- 基于分布式数据库,非关系性数据库中最接近关系型数据库。
- 库→集合→文档
- 服务器端开启服务
mongod.exe --dbpath C:\data\db(路径)
指定服务地址
- 开启客户端
mongod(在环境变量下)
- 创建数据库
use DB_NAME
- 插入数据(创建集合)
db.tb_name.insert({key:value})
- 查看数据库
show dbs;
- 删除数据库
db.dropDatabase()
- 创建集合
db.createCollection("set_name")
- 查看集合
show collections
- 删除集合
db.collection.drop()
Scrapy框架
- 建立爬虫的命令方式
scrapy genspider x www.x.com
这里使用的版本是1.0以上的版本,在填写爬虫域名的时候 不需要加引号
- 运行爬虫
scrapy crawl x
- 扩展:
- 查看爬虫项目的模式列表
>>>scrapy genspider -l Available templates: basic crawl csvfeed xmlfeed
- 使用上述模式中的一种,指定它,使用它进行开发(构建)爬虫文件。
spider.py文件:
爬虫文件是整个框架中我们最常编写的,里面包括一些内容需要我们学习。
extract()函数
- 代替etree将格式化后的源码内容输出
parse()函数
- parse() 是spider的一个方法。
- 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。
- 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
setting.py文件:
这里的配置有很多,但并不是我们所学的全部,譬如分布式设定,是要自己手写进去的
- robots.txt的规则遵循设定:这里设置是否遵循robots文件的协定,一般情况下设置成False就行
# Obey robots.txt rules
ROBOTSTXT_OBEY = False # default:True
- 设置全局Header头:在settings.py文件中搜索就可以找到,取消注释之后可以将headers头信息加入进去。(在DownloadMiddlewares【下载中间件】中也可以加headers请求头信息,cookie,代理等。)
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
- 设置爬取间隔:在制定范围内随机的秒数作为爬取间隔,下面这个五表示的是最大爬取间隔时间(框架会从中1~5秒中选取一个时间点),并不是每次爬取都使用这个数字作为间隔秒数。
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
- 开启入库入口文件piplines.py的使用:在setting.py中可以找到这些代码,将它解开,就可以使用。同时还可以在ITEM_PIPELINES字典中设置新的内容(这些内容就是piplines.py中新的类),并且给它设置不同的优先级。指数越低,优先级越高。
#ITEM_PIPELINES = {
# 'Csdnboke.pipelines.CsdnbokePipeline': 300,
#}
piplines.py文件:
一种去重方法
- 首先建立一个新的,使用逻辑实现去重,源代码如下:
# 导入模块
from scrapy.exceptions import DropItem
# 建立一个用于去重的类
class Set_it(object):
# 建立一个构造方法
def __init__(self):
self.set0 = set()
# 建立一个process_item方法,它会自动调用
def process_item(self, item, spider):
# 标题
title = item["title"]
# 判断标题是否已经存入了set中
if title in self.set0:
# 将这条item记录删除,并且输出一条提示信息
raise DropItem("这一条内容已经存在了,所以我把它删了:%s"%title)
# 否则将标题存入
else:
#
self.set0.add(title)
return item
第三方模块
newspaper(第三方爬虫包)
> newspaper是一个第三方包,用于快速抓取页面信息。它的开发团队来自于美国,现在还仍在维护当中。如果要使用,首先要保证在你的电脑上下载并安装这个包,对此,我们可以使用pip这样的包管理器进行直接下载安装,当然,用conda也可以。- 安装newspaper
pip install newspaper3k
- 在Python爬虫文件中导入它
import newspaper
- 使用实例化一个newspaper的Article方法,这个方法接收两个参数,第一个参数是Url,第二个参数是区域语言。
# 实例化Article
# demo:
# news = newspaper.Article()
news = newspaper.Article(url=base_url,language='zh')
- 执行Article的download方法保存源码,然后执行parse方法,这两步是目前必须的
news.download()
news.parse()
- 获取页面正文
content = news.text
- 获取页面标题
title = news.title
- 获取文章内配图
img = news.imges
Retrying 模块
retrying是一个第三方模块,用于重试出现异常的函数。
首先进行安装:
pip install retrying (安装)
代码示例(“出事了算我的,你尽管去做,做错了也不怕,但最多三次”):
from retrying import retry
# 作为装饰器 装饰函数,报错仍然重复执行,直到三次才算出错
@ retry(stop_max_attempt_number=3)
def fun1():
print("hello world.")
time.sleep(1)
raise ValueError("This is a test error.")
开发一个微型ORM(对象关系映射)脚本【源代码】
#coding=utf-8
'''微型ORM v1.1
@help:
首先要保证你的items中的键值与数据库中的字段吻合,其次要自己建立库和数据表,
传入你的item,并且传入表的名称
@Form: 魏泯
'''
def sql(item,table):
'''
item: Your dict.
table: Your table name.
'''
# 生成两个列表推导式
keylis = [key for key,values in item.items()]
vlis = [values for key,values in item.items()]
# 生成sql 语句中所需要的
str1215 = '"{}"'+',"{}"'*(len(item)-1)
str1217 = '{}'+',{}'*(len(item)-1)
str1216 = 'insert into '+table+'('+str1217+')'+' value ('+str1215+')'
_sql = str1216.format(*keylis,*vlis)
return _sql # 返回sql语句
——向前走吧,往往最珍贵的东西都不容易在表层浮现的事物找到。