爬虫的基础知识

爬虫的定义

只要是浏览器可以做的事情,原则上,爬虫都可以帮助我们做,即:浏览器不能够做到的,爬虫也不能做

网络爬虫:又叫网络蜘蛛(spider),网络机器人,就是模拟客户端发送网络请求,接受请求响应,一种按照一定的规则,自动地抓取互联网信息的程序

爬虫的分类

  • 通用爬虫

    通常指搜索引擎的爬虫(面对整个互联网)

  • 聚焦爬虫:

    针对特定网站的爬虫

  • 流程:

    爬虫的工作流程

ROBOTS协议

网站通过robots协议告诉搜索引擎那些页面可以抓取,那些页面不能抓取

例如:https://www.taobao.com/robots.txt(通常是网站后面加/robots.txt即可以看到,就是一个文本文件)

部分内容如下:

User-agent:  Baiduspider    #用户代理,可以理解为浏览器的身份标识,通过这个字段可以告诉服务器是什么样的程序在请求网站,Baiduspider即百度的搜索引擎
Allow:  /article    #表示允许爬的内容
Allow:  /oshtml
Allow:  /ershou
Allow: /$
Disallow:  /product/    #表示不允许该用户代理爬的内容
Disallow:  /

但是robots只是道德层面的约束

http和https

为了拿到和浏览器一模一样的数据,就必须要知道http和https

在这里插入图片描述
  • http:

    超文本传输协议,明文方式传输,默认端口80

  • https:

    http+ssl(安全套接字层),会对数据进行加密,默认端口443

https更安全,但是性能更低(耗时更长)

浏览器发送http请求的过程

在这里插入图片描述

ps:爬虫在爬取数据的时候,不会主动的请求css、图片、js等资源,就算自己爬取了js的内容,也只是字符串,而不会执行,故,浏览器渲染出来的内容和爬虫请求的页面并不一样

爬虫要根据当前url地址对应的响应为准,当前url地址的elements的内容和url的响应不一样,特别要注意tbody,经常在elements中有而响应中无

  • 页面上的数据在哪里?

    • 当前url地址对应的相应中
    • 其他url地址对应的相应中,如ajax请求
    • js生成:1、部分数据在响应中,2、全部由js生成

url的格式

host:服务器的ip地址或者是域名
port:服务器的端口
path:访问资源的路径
query-string:参数,发送给http服务器的数据
anchor:锚点(跳转到网页的制定锚点位置,anchor也有主播的意思)

例:http://item.jd.com/11936238.html#product-detail,就是一个带锚点的url,会自动跳转到商品详情,但是要注意,一个页面带锚点和不带锚点的响应是一样的(写爬虫的时候,就可以直接删掉锚点的部分)

http请求格式

在这里插入图片描述

如,在访问百度时,查看request headers的source时,就可以看到如下内容

GET http://www.baidu.com/ HTTP/1.1  
#请求方法:get;url:http:xxxx.com/;协议版本:http1.1,然后换行
Host: www.baidu.com
#请求头部:host;值:www.baidu.com;换行,以下类似
Proxy-Connection: keep-alive    #keep-alive表示支持长链接。为什么要用长连接:不用频繁握手挥手,提高效率
Upgrade-Insecure-Requests: 1    #升级不安全的请求:把http请求转换为https的请求
DNT: 1  #Do not track
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)    Chrome/76.0.3809.100 Safari/537.36  #浏览器的标识。名字/版本号。如果有模拟手机版的请求,改user agent即可,不同的user agent访问相同的url,可能会得到不同的内容
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3  #浏览器告诉服务器自己可以接受什么样的数据
Referer: http://baidu.com/
Accept-Encoding: gzip, deflate  #告诉服务器自己可以接受什么样的压缩方式
Accept-Language: en-US,en;q=0.9 #告诉服务器自己可以接受什么样语言的数据,q:权重,更愿意接受哪种语言
Cookie: BAIDUID=B8BE58B25611B7BBA38ECFE9CE75841F:FG=1; BIDUPSID=B8BE58B25611B7BBA38ECFE9CE75841F; PSTM=1565080210; BD_UPN=12314753; delPer=0; BD_HOME=0;    H_PS_PSSID=26522_1453_21118_29523_29521_29098_29568_28830_29221_26350_22159; BD_CK_SAM=1; PSINO=7; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; COOKIE_SESSION=218_0_3_0_0_7_1_1_1_3_76_2_0_0_0_0_0_0_1565599571%7C3%230_0_1565599571%7C1; rsv_jmp_slow=1565599826516; H_PS_645EC=2c80At1Is237xdMOfC3ju2q0qlWJ%2FFlbD5N50IQeTrCHyIEsZN6yQYBgLHI; B64_BOT=1   #cookie:保存用户的个人信息。ps:cookie和session的区别:cookie保存在浏览器本地,不安全,存储量有上限,session保存在服务器,更安全,往往没有上限。cookie又分为request cookie和reponse cookie,在浏览器中可以查看

除了以上字段,可能还有referer字段,表示当前url是从哪个url过来的;x-request-with字段,表示是ajax异步请求

以上字段中,主要是user agent(模拟浏览器),cookie(反反爬虫)

常见的请求方式

  • get:除了post,基本都用get,更常用

  • post:常用于提交表单的时候(安全),传输大文件的时候(美观)

响应状态码(status code)

  • 200:成功
  • 302/307:临时转移至新的url
  • 404:not found
  • 500:服务器内部错误

字符串知识复习

  • str类型和bytes类型

    • bytes:二进制类型,互联网上数据都是以二进制的方式传输的
  • str:unicode的呈现形式

ps:ascii码是一个字节,unicode编码通常是2个字节,utf-8是unicode的实现方式之一,是一变长的编码方式,可以是1、2、3个字节

编码和解码的方式必须一致,否则会乱码

爬虫部分重要的是理解,而不是记忆


Request模块使用入门

Q:为什么要学习requests,而不是urllib?

  1. requests的底层实现是就urllib,urllib能做的事情,requests都可以做;
  2. requests在python2和python3中通用,方法完全一样;
  3. requests简单易用;
  4. request能够自动帮我们解压(gzip等)网页内容

中文文档api:http://docs.python-requests.org/zh_CN/latest/index.html

基础使用

"""基础入门"""
import requests

r = requests.get('http://www.baidu.com')    #r即为获得的响应。所请求的所有东西都在r中
# 必须要包含协议(http或https);还有dlelete,post等方法

print(r)

print(r.text)   #text是一个属性,其实可以通过他的意思判断,text是一个名字,所以是属性,如果是方法,常为动词
#会自动根据推测的编码方式解码为str

print(r.encoding)   #根据头部推测编码方式,如果猜错了,我们解码的时候就必须自己指定解码的方式

print(r.content)    #也是属性,是一个bytes类型的数据。
print(r.content.decode())   #将bytes类型转换为str类型。默认的解码方式为utf-8
print(r.status_code)    #获取状态码
assert r.status_code == 200   #断言状态码为200,如果断言失败,会报错:assertionerror
#可以用此方法判断请求是否成功

print(r.headers)    #响应头,我们主要关注其中的set-cookie(在本地设置cookie)字段
print(r.request)    #关于对应相应的请求部分,是一个对象
print(r.request.url)    #请求的url地址
print(r.url)    #响应地址,请求的url地址和响应的url地址可能会不一样(因为可能和重定向)
print(r.request.headers)    #请求头,如果不设置,默认的user-agent是python-requests/x.xx.x

with open('baidu_r.txt','w') as f:    #测试:查看默认的user-agent访问时返回的内容
    f.write(r.content.decode())

"""
requests中解编码的方法:
1. r.content.decode()   #content的类型为bytes,必须再次解码
2. r.content.decode('gbk')
3. r.text       #text是按照推测的编码方式进行解码后的数据,他的类型为str
"""

发送带header的请求

具体的header到浏览器中进行查看

"""
为什么请求需要带上header?
    模拟浏览器,欺骗服务器,获取和浏览器一致的内容

header的形式:字典,形式:{request headers冒号前面的值:request headers冒号后面的值},大部分情况,我们带上user-agent即可,少数情况需要cookie
用法:requests.get(url,headers=headers) 
"""

import requests

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}

response = requests.get('https://www.baidu.com',headers=headers)

print(response.content.decode())    #会发现响应中的数据比不带header多许多

发送带参数的请求

#在url中带参数的形式
#例如:在我们百度搜索某东西时,就会带上一大堆参数,但是大部分可能是没有用的,我们可以尝试删除,然后我们在爬虫中带的参数只需要为其中不能删除的部分即可
"""
参数的形式:字典
kw={'wd':'长城'}
用法:requests.get(url,params=kw)
"""
import requests

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}

params = {'wd':'这里是中文'}
#如果参数含中文,其就会被自动编码,编码的后的形式含百分号,我们可以使用url解码来查看原来的内容

r = requests.get('https://www.baidu.com',params=params,headers=headers)
print(r.status_code)
print(r.request.url)
print(r.url)
print(r.content.decode())

#当然,我们也可以直接把参数拼接到url中去,而不单独传参(也不需要手动编码),eg:r = requests.get('https://www.baidu.com/s?wd={}'.formate('传智播客'))

小练习:爬贴吧前1000页

import requests
kw = input('请输入您要爬取的贴吧名:')
url = 'https://tieba.baidu.com/f?kw=%{kw}8&pn='.format(kw=kw)
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}


for i in range(1000):
    url = urlr.formate(str(i*50))
    r = requests.get(url=url, headers=headers)
    with open('./tieba_pages/{}-第{}页.html'.format(kw,i), 'w', encoding='utf-8') as f:
        # 为什么是utf-8,因为r.content.decode()为utf-8的格式
        f.write(r.content.decode())

扁平胜于嵌套:比如,多用列表推倒式替代某些循环


Request深入

发送post请求

用法:

response = requests.post(‘https://www.baidu.com’,data=data,headers=headers)
post时不仅需要地址,还需要我们post的数据,该数据就放在data中
data的形式:字典

使用代理

在这里插入图片描述

正向代理与反向代理

在这里插入图片描述

反向代理:浏览器不知道服务器的地址,比如以上的图例中,浏览器知道的只是nginx服务器,因此,及时有攻击,也只能攻击nginx,不能攻击到我们的服务器

正向代理:浏览器知道服务器的地址

爬虫为什么要使用代理

  • 让服务器以为不是同一个客户端在请求
  • 防止我们的真实地址被泄漏,防止被追究

使用代理

用法:requests.get(‘http://www.baidu.com’,proxies=proxies)
proxies的形式是字典

proxies={
   'htttp':'http://12.34.56.78:8888', #如果请求的是http
   'https':'https://12.34.56.78:8888' #如果请求的是https的地址
}

免费代理的网址:https://proxy.mimvp.com/free.php

代理一般可以分为3种:

  • 透明代理
  • 普匿代理,透明以及普匿,对方是可以追查到我们的真实ip的
  • 高匿代理

要注意,不是所有的ip都支持发送https的请求,有些也不支持发送post请求

代码示例:

"""
0. 准备大量ip地址,组成ip池,随机选择一个ip地址来使用
    - 如何随机选择ip
        - {'ip':ip,'times':0}
        - [{},{},...{}],对这个ip的列表按照使用次数进行排序
        选择使用次数较少的几个ip,从中随机选择一个
1. 检查代理的可用性
    - 使用request添加超时参数,判断ip的质量
    - 在线代理ip质量检测的网站
"""
import requests

proxies = {"http":'http://123.56.74.13:8080'}

headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'
}

r = requests.get('http://www.baidu.com', proxies=proxies,headers=headers)

print(r.status_code)
print(r.request.url)

session和cookie的使用与处理

cookie和session的区别

  • cookie存储在客户的浏览器上,session存储在服务器上
  • cookie的安全性不如session,别人可以分析存放在本地的cokie并进行cookie欺骗
  • session会在一定时间内保存在服务器上,当访问增多,会比较比较占用服务器的性能
  • cookie保存的数据容量有限(通常是4k),很多浏览器限制一个站点最多保存20个cookie

爬虫处理cookie和session

  • 带上 cookie和session的好处:

    能够请求到登录之后的页面

  • 带上cookie和session的弊端:

    一套cookie和session往往和一个用户对应,请求太快、次数太多,容易被服务器识别为爬虫

不需要cookie的时候尽量不去使用cookie

但是为了获取登录之后的页面,我们必须发送带有cookies的请求

携带cookie请求:

  • 携带一堆cookie进行请求,把cookie组成cookie池

如何使用

requests提供了一个叫做session的类,来实现客户端和服务器端的会话保持

  1. 实例化一个session对象:session = requests.session()
  2. 让session发送get或post请求:r = sessioon.get(url=url,data=post_data, headers=headers)

请求登录之后的网站的思路:

  • 实例化session
  • 先使用session发送请求,登陆对应网站,把cookie保存在session中,这里请求时,url应是表单的action的值,如果没有action项,就尝试抓包,看看当我们提交的时候,究竟给哪个网址发送了post请求;post_data是表单中的要提交的数据,其键为name
  • 再使用session请求登录之后才能访问的网站,session能够自动的携带登录成功时保存在其中的cookie,进行请求

案例:访问淘宝的登录后的页面

import requests

sesssion = requests.session()

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}
#注意:在copy User-Agent时,一定要复制全,不能直接在查看的时候copy,容易带上省略号
post_url = 'https://login.m.taobao.com/login.htm'
#post的url一般是在源码中表单的action中找

post_data = {
    'TPL_username':'xxxx',
    'TPL_password2':'xxxx'
}#表单中要填写的项

sesssion.post(url=post_url, data=post_data, headers=headers)

r = sesssion.get('https://h5.m.taobao.com/mlapp/mytaobao.html',headers=headers)

with open('taobao.html', 'w', encoding='utf-8') as f:
    f.write(r.content.decode()) #会发现taobao.html中的代码与我们登录淘宝后的https://h5.m.taobao.com/mlapp/mytaobao.html的代码一样,即成功访问了登录淘宝后的页面

不发送post请求,使用cookie获取登录后的页面

即:直接将cookie加在headers里面,而不必使用session进行post

如:

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
    'Cookie':'xxxx'
}
url = 'https://i.taobao.com/my_taobao.htm'

r = requests.get(url=url, headers=headers)

print(r.content.decode())

也可以对cookies以参数形式传递,cookies为字典

r = requests.get('http://xxxx',headers=headers, cookies=cookies)
  • cookie过期时间很长的
  • 在cookie过期之前能够拿到所有的数据,比较麻烦
  • 配合其他程序一起使用,其他程序专门获取cookie,当前程序专门请求页面

寻找登录的post地址

  • 在form表单中查找actiond的url地址

    • post的数据是input标签中的name的值作为键,真正的用户名密码作为值的字典,post的url地址就是action对应的url地址
  • 抓包,看看当我们提交的时候,究竟给哪个网址发送了post请求

    • 勾选perserve log按钮,防止页面跳转找不到url
    • 寻找post数据,确定参数

      • 参数不会变:(如何确定参数会不会变?多请求几次),直接用,比如密码不是动态加密的时候
      • 参数会变

        • 参数在当前的响应中
        • 通过js生成:定位到对应的js查看

定位想要的js

  • 法一:对于Chrome浏览器

    • 选择登录按钮(或任意绑定了js事件的对象)
    • Eventlistener
    • 勾选Framework listeners
    • 查看对应的js
    • 找到登录按钮对应的函数
    • (如果遇到某个元素(如:$(‘.password’).value)是干嘛的,可以copy到console中去进行查看;也可以直接对js添加断点)
  • 法二:对于Chrome浏览器

    • 直接通过Chrome中的search all file的搜索url中的关键字
  • 法三

    添加断点的方式来查看js的操作,通过python进行同样的操作,就可以得到js生成的数据

Requests的小技巧

cookie对象与字典的相互转化与url编解码

"""1. 把cookie对象(cookiejar)转化为字典"""
import requests
r = requests.get('http://www.baidu.com')
print(r.cookies)
ret = requests.utils.dict_from_cookiejar(r.cookies)
print(ret)  #输出:{'BDORZ': '27315'}
"""将字典转化为cookiejar"""
print(requests.utils.cookiejar_from_dict(ret))

"""2. url地址的编解码"""
url = 'http://www.baidu.com'
print(requests.utils.quote(url))    #输出:http%3A//www.baidu.com
print(requests.utils.unquote(requests.utils.quote(url)))    #输出:http://www.baidu.com

"""输出结果如下:
]>
{'BDORZ': '27315'}
]>
http%3A//www.baidu.com
http://www.baidu.com
"""

请求SSL证书验证与超时设置

如果某网站使用的是https,但是又没有购买ssl证书,等浏览器访问时就会提示不安全,而当我们使用爬虫爬取的就会报错,此时,我们可以用verify=False来解决

import requests
r = requests.get('https://www.12306.cn/mormhweb/',verify=False, timeout=10) #如果超时,会报错,因此要结合try使用
"""注意:此时不会报错,但是会warning"""

配合状态码判断是否请求成功

assert response.status_code == 200  #如果断言失败,会报错,因此应该结合try使用

重新请求

使用retrying模块,通过装饰器的方式使用

"""重新请求"""
import requests
from retrying import retry

headers= {
            'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'
        }
@retry(stop_max_attempt_number=3)   #即下面的方法最多可以报错3次,如果3次都报错,则程序报错
def _parse_url(url):
    r = requests.get(url,headers=headers,timeout=0.01)
    assert r.status_code == 200
    return r.content.decode()

def parse_url(url):
    try:
        html_str = _parse_url(url)
    except:
        html_str = None
    return html_str
if __name__ == "__main__":
    url = 'http://www.baidu.com'
    print(parse_url(url))

ps:安装第三方模块的方法

  • pip install
  • 下载源码文件,进入解压后的目录:python setup.py install
  • xxx.whl文件,安装方法:pip install xxx.whl

数据提取方法

基础知识

什么是数据提取

从响应中获取我们想要的数据

数据的分类

  • 非结构话数据:html等

    • 处理方法:正则表达式、xpath
  • 结构化数据:json、xml等

    • 处理方法:转化为python数据类型

主要是看结构清不清晰

数据提取之JSON

由于把json数据转化为python内奸数据类型很简单,所以爬虫缀,我们常使用能够返回json数据的url

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它使得人们很容易进行阅读和编写 。同时也方便了机器进行解析和生成,适用于进行数据交换的场景,比如网站前后台间的数据交换

Q:哪里能够找到返回json的url呢?

  • 使用chrome切换到手机页面
  • 抓包手机app的软件
在这里插入图片描述

json.loads与json.dumps

"""
1. json.loads   能够把json字符串转换成python类型
2. json.dumps   能够把python类型转换为json字符串,当我们把数据保存问文本的时候常常需要这么做,如果要使其显示中文,可以使用参数:ensure_ascii=False;还使用使用参数:indent=2,使下一级相对上一级有两个空格的缩进
"""

使用json的注意点:

  • json中的引号都是双引号;

    • 如果不是双引号

      • eval:能实现简单的字符串和python类型的转化
      • replace:把单引号替换为双引号
  • 往一个文件中写如多个json串,不再是一个json串

    • 可以一行写一个json串,按照行来读取

json.load与json.dump

类文件对象:具有read和write方法的对象就是类文件对象,比如:f = open(‘a.txt’,’r’),f就是类文件对象(fp)

"""
1. json.load(类文件对象) #类型为dict
2. json.dump(python类型, 类文件对象)   #把python类型放入类文件对象中,也可以使用ensure_ascii和indent参数
"""

json在数据交换中起到了一个载体的作用,承载着相互传递的数据

在这里插入图片描述

案例:爬取豆瓣

import requests
from pprint import pprint   #pprint:pretty print,实现美化输出
import json

from retrying import retry

url = 'https://m.douban.com/rexxar/api/v2/skynet/playlists?from_rexxar=true&for_mobile=1'

headers = {
    'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36',
    # 'Sec-Fetch-Mode': 'cors'
    'Referer': 'https://m.douban.com/movie/beta'
    #在本次爬取过程中,必须加上Referer才行
}
@retry(stop_max_attempt_number=3)
def parse_url(url):
    r = requests.get(url,headers=headers, timeout=10)
    assert r.status_code == 200
    return r.content.decode()
    
resp_html = parse_url(url)
p_resp = json.loads(resp_html)
pprint(p_resp)
with open('douban.json','w', encoding='utf-8') as f:
    f.write(json.dumps(p_resp, indent=2, ensure_ascii=False))

douban.json中的部分内容如下:

在这里插入图片描述

案例:爬取36kr

"""爬取36kr"""
import requests,json
from pprint import pprint
import re
url = 'https://36kr.com/'

headers = {
    'User-Agent':'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36'
}
r = requests.get(url=url, headers=headers,timeout=3)
html_str = r.content.decode()
reg = '(.*?)'    #新闻的标题是直接在html中的
ret = re.findall(reg, html_str)
pprint(ret)

部分输出结果如下:

在这里插入图片描述

爬虫思路总结

  • 通常,我们访问某个网站时,得到的是其主页的url
  • 得到了主页的url后,观察我们所需要的数据是否在主页对应的响应中,如果在,直接利用主页的url爬取
  • 如果不在主页的url中,查找我们需要的数据,得到其对应url,用该url进行数据的爬取
  • 如果相应数据不是在html中,而是json中,用json.loads对数据进行处理

正则表达式复习

所谓正则表达式,即:事先定义好一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤

常用的正则表达式的方法有

  • re.compile:编译
  • pattern.math:从头找一个
  • pattern.search:找一个
  • pattern.findall:找所以
  • pattern.sub:替换
在这里插入图片描述

说明:

  • .的Dotall即模式即是在匹配时加上re.Dotall参数,或者re.S,使.能够匹配任意字符
  • 记忆:d:digit;s:space

  • sub的使用,re.sub(reg, new_str, old_str),将匹配到的内容替换为new_str

  • re.findall(‘a(.*)b’, ‘str’),能够返回括号中的内容,括号前后的内容起到定位和过滤的效果

  • r’anb’ 可以匹配’anb’;r’anb’而不能匹配’anb’,r可以忽略转义符号带来的影响,待匹配的字符串里面有 几个,正则表达式里面也写几个即可

  • compile的作用

    • 将对应正则表达式能够匹配到的内容放到内存中去,加快匹配的速度
    • 使用方法:re.compile(reg)
  • compile和sub的结合使用

    b = hello1world2
    p = re.compile('d')
    p.findall(b)
    p.sub('_',b)    #将b中的所有数字替换为下划线
    

    ps:如果是对.进行编译,若想使其能够匹配换行符等,则re.S需要加在编译的使用,而不是匹配的时候

贪婪模式与非贪婪模式

  • 非贪婪模式:.*?或者.+?
  • 贪婪模式:.*或者.+

XPATH和lXML

基础知识

lxml是一款高性能的python HTML/XML解析器,利用xpath,我们可以快速的定位特定元素以及获取节点信息

什么是xpath

xpath(XML Path Language)是一门在HTML/XML文档中查找信息的语言(既然是一种语言,就有自己的语法),克用来在html/xml中对元素和属性进行遍历

W3School官方文档:http://www.w3school.com.cn/xpath/index.asp

xml与html对比

在这里插入图片描述

节点的概念:每个xml标签都称之为节点,比如下图中的

在这里插入图片描述

book节点是title等节点的父节点,title和author等是兄弟节点,此外,还有祖先节点等概念

常用节点选择工具

  • Chrome插件XPATH Helper
  • 开源的XPATH表达式编辑工具:XMLQuire(XML格式文件可用)
  • FireFox插件 XPath Checker

XPATH语法

XPATH使用路径表达式来选取xml文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常类似

注意:我们写xpath的时候,看的是请求页的响应,而不是elements

在这里插入图片描述

使用Chrome插件的时候,当我们选中标签,该标签会添加属性class=’xh-highlight’

/html   即表示从根节点开始选中html标签
/html/head  选中html标签下的head标签
/html/head/link 选中html标签下的head标签中的所有link标签

xpath学习重点

  • 使用xpath helper或者是Chrome浏览器中的copy xpath都是从element中提取的数据,但是爬虫获取的是url对应的响应,往往和elements不一样
  • 获取属性

    • /html/head/link/@href 选择html标签下的head标签下的(所有)link标签中的href属性的值
  • 获取文本

    • /html/head/link/text() 即选取标签中的内容,innerHtml
    • /html//text() 获取html下所有的标签文本
    • //a[text()='下一页']选择文本为下一页三个字的a标签
  • 从当前节点往下开始选择与使用@进行元素的定位

    • //li 选中文档中所有的li标签
    • //li//a 文档中的所有li中的所有a标签
    • //ul[@id='detail-list']/li 选中文档中的id为’detail-list’的ul标签下的li标签;如果没有id,也可以@class等

选择特定节点

在这里插入图片描述

上图中的部分说明:

  • /bookstore/book[price>35.00]book用的是子节点中的price标签进行的修饰,此处price的形式为:35.00

选择未知节点

在这里插入图片描述

选择若干路径(或的运用)

在这里插入图片描述

lXML库

使用入门

"""
1. 导入lxml的etree库:from lxml import etree,注意,如果是在pycharm中,可能会报红,但是不影响使用
2. 利用etree.HTML,将字符串转化为Element对象
3. Element对象具有xpath的方法:html = etree.HTML(text)    html.xpath('字符串格式的xpath语法')
"""

应用举例:

from lxml import etree
from pprint import pprint

text = """
             
586万

            

539万

            

444万

            

395万

"""
html = etree.HTML(text)
#html为一个Element对象
pprint(html)
#查看element对象中包含的字符串(bytes类型)
pprint(etree.tostring(html).decode())   #会发现把缺少的标签进行了补全,包括html和body标签
print(html.xpath('//td/text()'))   #这里的html是上面etree.HTML(text)获得的对象,结果为列表
#只要是element对象,就可以使用xpath进行数据的提取

lxml注意点
  • lxml可以自动修正html代码(但是不一定能正确修正,也可能改错了)
    • 使用etree.tostring查看修改后的样子,根据修改之后的html字符串写xpath
  • 提取页面数据的思路
    • 先分组,渠道:一个包含分组标签的列表
    • 遍历:取其中每一组进行数据的提取,不会造成数据对应错乱
xpath的包含
  • //div[contains(@class='li')]获取包含有li样式类的标签的标签
爬虫的思路总结
  1. 准备url
    1. 准备start_url
      • url地址规律不明显,总数不确定
      • 通过代码提取下一页url
        • xpath:url在当前的响应里面
        • 寻找url地址,部分参数在当前的响应中,比如当前页面数和总的页码数(eg:通过js生成)
    2. 准备url_list
      • 页码总数明确
      • url地址规律明显
  2. 发送请求,获取响应
    • 添加随机的User-Agent:反反爬虫
    • 添加随机的代理ip:反反爬虫
    • 在对方判断出我们是爬虫之后,添加更多的header字段,包括cookie
      • cookie的处理可以通过session来解决
      • 准备一堆能用的cookie,组成cookie池
        • 如果不登录,准备当开始能够请求对方网站的cookie,即接受对方网站设置在response的cookie
          • 下一次请求的时候,使用之前的列表中的cookie来请求
          • 即:专门用一个小程序来获取cookie,爬取数据再用另一个程序
        • 如果登录
          • 准备一堆账号
          • 使用程序获取每个账号的cookie
          • 之后请求登录之后才能访问的网站随机的选择cookie
  3. 提取数据
    • 确定数据的位置
      • 如果数据在当前的url地址中
        • 提取的是列表页的数据
          • 直接请求列表页的url地址,不用进入详情页
        • 提取的是详情页的数据
          • 确定url地址
          • 发送请求
          • 提取数据
          • 返回
      • 如果数据不在当前的url地址中
        • 在其他的响应中,寻找数据的位置
        • 使用chrome的过滤条件,选择除了js,css,img之外的按钮(但是可能出错)
        • 使用chrome的search all file,搜索数字和英文(有时候不支持搜索中文)
    • 数据的提取
      • xpath,从html中提取整块数据,先分组,之后没一组再提取
      • json
      • re,提取max_time,price,html中的json字符串
  4. 保存
    • 保存在本地,text、json、csv
    • 保存在数据库

CSV

逗号分隔值,一种文件后缀,以纯文本的形式存储表格数据 其文件中的一行对应表格的一行,以逗号分隔列

多线程爬虫

在这里插入图片描述

动态HTML技术

在这里插入图片描述

Selenium和PhantomJS

  • Selenium Selenium是一个Web的自动化测试工具,可以控制一些浏览器(比如phantomJS),可以接受指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏
  • PhantomJS phantomJS是一个基于Webkit的“无界面”浏览器,它会把网站加载到内存并执行页面上的javascript
下载地址:http://phantomjs.org/download.html

入门

"""1. 加载网页"""
from selenium import webdriver
driver = webdriver.PhantomJS("xxxx/phantom.exe")
"""除了PhantomJS,还有Chrome,FireFox等"""
driver.get("http://www.baiud.com/")
driver.save_screenshot("长城.pnh")

"""2. 定位和操作"""
driver.find_element_byid("kw").send_keys("长城")
drvier.finde_element_by_id("su").click()

"""3. 查看和请求信息"""
driver.page_source()
driver.get_cookies()
driver.current_url()

"""4. 退出"""
driver.close()  #退出当前页面
driver.quit()   #退出浏览器

"""5. 其他"""
#ps:无论是使用PhantomJS还是Chrome或是FireFox,driver的操作是一样的

基础使用示例

ps:chromedriver的下载地址(注意:版本一定要和你安装的Chrome浏览器的版本号一致):http://npm.taobao.org/mirrors/chromedriver/

from selenium import webdriver
"""
selenium请求的速度很慢,因为是使用浏览器,会请求js、css等
"""

phantom_path = r"D:Greenphantomjs-2.1.1-windowsbinphantomjs.exe"
"""在使用phatnomjs时,报了unexpected exit, status code 0的错误,尚未找到原因"""
chrome_path = r"C:Users25371Desktopchromedriver_win32chromedriver.exe"
"""注意:这里一定要是chromedriver.exe,而不是chrome.exe"""

driver = webdriver.Chrome(executable_path=chrome_path) #实例化对象
# driver.maximize_window()    #最大化窗口
driver.set_window_size(width=1920,height=1080)  #设置窗口大小
driver.get("http://www.baidu.com")
driver.find_element_by_id('kw').send_keys("python")
#kw是百度的输入框的表单的id;send_keys就是往一个input标签里面输入内容
#以上的一行代码就可以时Chrome自己百度搜索python
driver.find_element_by_id('su').click()
#su是百度一下的按钮的id
#click实现对按钮的点击

"""获取当前的url"""
print(driver.current_url)   #注意:因为已经click了,所以是click后的地址

"""截屏"""
driver.save_screenshot("./baidu_python.png")
"""在本次截屏中,由于截屏太快而网页加载太慢,截屏的图中未能截到百度出来的结果"""

"""driver获取cookie"""
cookies = driver.get_cookies()
print(cookies)
cookies = {i['name']:i['value'] for i in cookies}  #使用字典推导式重新生成requests模块能用的cookies
print(cookies)

"""获取html字符串"""
"""即elements"""
print(driver.page_source)   #page_source是一个属性,获得html字符串后,就可以直接交给xpath

"""退出当前页面"""
driver.close()  #如果只有一个窗口,close就是退出浏览器
"""退出浏览器"""
driver.quit()

示例二

from selenium import webdriver
from time import sleep
chrome_path = r"C:Users25371Desktopchromedriver_win32chromedriver.exe"
driver = webdriver.Chrome(executable_path=chrome_path)
driver.get('https://www.qiushibaike.com/text/page/1/')
ret = driver.find_elements_by_xpath(".//div[@id='content-left']/div")
for r in ret:
    print(r.find_element_by_xpath("./a[1]/div[@class='content']/span").text)
    """通过text属性获取文本"""
    print(r.find_element_by_xpath("./a[1]").get_attribute("href"))
    """通过get_attribute获取属性"""

driver.quit()

元素的定位方法

  • find_element_by_id 返回一个
  • find_elements_by_id 返回一个列表
  • find_elements_by_link_text
  • find_elements_by_partial_link_text
  • find_elements_by_tag_name
  • find_element_by_class_name
  • find_elements_by_class_name
  • find_elements_by_css_selector

注意:

  • 获取文本或属性时,需要先定位到对应元素,再使用text属性或者get_attribute方法
  • element返回一个,elements返回列表
  • link_text和partial_link_text的区别:全部文本和包含的某个文本,即partial可以只写部分文本,而link_text需要写完整
  • by_css_selector的用法:#food span.dairy.aged
  • by_xpath中获取属性和文本需要使用get_attribute()和.text
  • selenium使用class定位标签的时候,只需要其中的一个class样式名即可,而xpath必须要写所有的class样式类名

示例:

from selenium import webdriver
from time import sleep
chrome_path = r"C:Users25371Desktopchromedriver_win32chromedriver.exe"

driver = webdriver.Chrome(executable_path=chrome_path)

driver.get('https://www.qiushibaike.com/text/page/1/')

ret = driver.find_elements_by_xpath(".//div[@id='content-left']/div")
for r in ret:
    print(r.find_element_by_xpath("./a[1]/div[@class='content']/span").text)
    """通过text属性获取文本"""
    print(r.find_element_by_xpath("./a[1]").get_attribute("href"))
    """通过get_attribut获取属性"""

print('-'*50)
"""find_element_by_link_text"""
"""根据标签里面的文字获取元素"""
print(driver.find_element_by_link_text("下一页").get_attribute('href'))
"""partial_link_text"""
print(driver.find_element_by_partial_link_text("下一").get_attribute('href'))
"""以上两行代码获得的东西相同"""
driver.quit()

深入

iframe

iframe或frame里面的html代码和外面的html代码实质上是两个不同的页面,因此,有时候我们在定位元素时,明明elements里面有,但是会定位失败

解决办法:使用driver.switch_to.frame或driver.switch_to_frame(已经被弃用)方法切换到对应frame中去

driver.switch_to.frame的使用说明:

def frame(self, frame_reference):
"""
        Switches focus to the specified frame, by index, name, or webelement.

        :Args:
         - frame_reference: The name of the window to switch to, an integer representing the index,
                            or a webelement that is an (i)frame to switch to.

        :Usage:
            driver.switch_to.frame('frame_name')
            driver.switch_to.frame(1)
            driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0])
        """
        源码

代码示例:豆瓣登录

from selenium import webdriver
import time
chrome_path = r"C:Users25371Desktopchromedriver_win32chromedriver.exe"
driver = webdriver.Chrome(executable_path=chrome_path)

driver.get("https://douban.com/")
login_frame = driver.find_element_by_xpath("//iframe[1]")
driver.switch_to.frame(login_frame)
driver.find_element_by_class_name("account-tab-account").click()
driver.find_element_by_id("username").send_keys("784542623@qq.com")
driver.find_element_by_id("password").send_keys("zhoudawei123")

driver.find_element_by_class_name("btn-account").click()
time.sleep(10)   #暂停以手动进行验证

"""获取cookies"""
cookies = {i['name']:i['value'] for i in driver.get_cookies()}
print(cookies)

time.sleep(3)
driver.quit()

注意

  • selenium获取的页面数据 是浏览器中elements的内容
  • selenium请求第一页的时候,会等待页面加载完了之后再获取数据,但是在点击翻页之后,会立马获取数据,此时可能由于页面还没有加载完而报错

其他

  • cookies相关用法

    • {cookie['name']:cookie['value'] for cookie in driverr.get_cookies()} 获取字典形式的cookie
    • driver.delete_cookie('cookiename')
    • driver.delete_all_cookies()
  • 页面等待

    页面等待的原因:如果网站采用了动态html技术,那么页面上的部分元素出现时间便不能确定,这个时候就需要一个等待时间

    • 强制等待:time.sleep(10)
    • 显示等待(了解)
    • 隐式等待(了解)

Tesseract的使用

  1. tesseract是一个将图像翻译成文字的OCR库,ocr:optical character recognition
  2. 在python中安装tesseract模块:pip install pytesseract,使用方法如下:

    import pytesseract
    from PIL import Image
    image = Image.open(jpg) #jpg为图片的地址
    pytesseract.image_to_string(image)
    

使用tesseract和PIL,我们就可以使程序能够识别验证码(当然,也可以通过打码平台进行验证码的识别)

示例:对如下图片进行识别

在这里插入图片描述
import pytesseract
from PIL import Image
img_url = "verify_code.jpg"
pytesseract.pytesseract.tesseract_cmd = r'D:Tesseract-OCRtesseract.exe'
image = Image.open(img_url)

print(pytesseract.image_to_string(image))

"""
使用过程中的问题:
    1. 电脑上必须先安装tesseract客户端,然后才能结合pytesseract使用
    2. 将tesseract加入环境变量
    3. 在环境变量中新建项:名字:TESSDATA_PREFIX,值:你的tesseract的安装目录(tessdata的父级目录)
    4. 在代码中加入:pytesseract.pytesseract.tesseract_cmd = r"tesseract的安装路径tesseract.exe"
    5. 默认只识别英语,如果要识别其他语言,需要下载相关语言的.traneddata文件到Tesseract的安装目录下的tessdata路径下:https://github.com/tesseract-ocr/tesseract/wiki/Data-Files
    
"""
"""识别结果如下:
Happy
Birthday
"""

Mongodb

注意:以下用到的集合大多为stu(学生信息),少部分为products

基础入门

Mongodb是一种NoSQL数据库

mysql的扩展性差,大数据下IO压力大,表结构更改困难;而nosql易扩展,大数据量高性能,灵活的数据模型,高可用

下载地址:https://www.mongodb.com/download-center/community

mongodb的使用

  • 在终端运行MongoDBbinmongod.exe --dbpath D:MongoDBdata,其中,D:MongoDB是安装路径,(注意:下图中我在安装时把MongoDB写成了MonggoDB

    在这里插入图片描述
    • 因为在安装时我们默认安装了MongoDB Compass Community,我们打开该软件,直接连接即可,不用对其做任何更改,成功后如图所示:
    在这里插入图片描述
    • 如果我们要使用数据库,还需要将安装目录下小bin目录加入系统的环境变量,然后在终端输入mongo即可

ps:在mongo的交互环境中,可以通过tab键进行命令的补全

database的基础命令

  • 查看当前数据库:db
  • 查看所有的数据库:show dbs或show databases
  • 切换数据库:use db_name
  • 删除当前数据库:db.dropDatabase()

在mongodb里面,是没有表的概念的,数据是存储在集合中

向不存在的集合中第一次插入数据时,集合会被创建出来

手动创建集合:

  • db.createCollection(name,options)
  • options是一个字典,例如:{size:10, capped:true} #表示存储的上限为10条数据,capped为true表示当数据达到上限时,会覆盖之前的数据
  • 查看集合:show collections
  • 删除集合:db.collection_name.drop()

mongodb中的数据类型

  • ObjectID:文档id,所谓文档,即我们即将存储到数据库中的一个个的字典
  • String
  • Boolean:必须是小写,true或false
  • Integer
  • Double
  • Arrays
  • Object:用于嵌入式的文档,即一个值为一个文档
  • Null
  • Timestamp:时间戳
  • Date:创建日期的格式:new Date("2019-02-01")

注意点:

  • 每个文档的都有一个属性,为_id,保证文档的唯一性
  • 可以自己设置_id插入文档,如果没有提供,自动生成,类型为Object_id
  • objecID是一个12字节的16进制数,4:时间戳,3:机器id,2:mongodb的服务进程id,3:简单的增量值

数据的操作

  • 用insert插入:先use数据库,db.集合名.insert({"name":"zhang3", "age":23}),实质上插入的数据不是字典,而是json,因此键可以不用引号。insert的时候如果文档id已经存在,会报错
  • 查看表中的数据:db.表名.find()
  • 用save进行数据的插入:db.集合名.save(要插入的数据),如果文档id(对应我们要插入的数据)已经存在,就是修改,否则新增
  • 查看集合中的数据:db.集合名称.find()
  • 更新:db.集合名称.update(,,{multi:}),用法如下:
    db.stu.upate({name:'hr'}, {name:'mnc'})#更新一条的全部
    db.stu.update({name:'hr'}, {set:{name:'mnc'}}) #更新一条中的对应键值
    """这种更改用得更多"""
    db.stu.update({},{set:{gender:0}}, {multi:true})   #更新全部
    """注意:multi这个参数必须和$符号一起使用才有效果"""
    
  • 使用remove删除数据:db.集合名.remove({name:"zhang3"},{justOne:true}),表示只删除一条名字为zhang3的数据,如果不指定justOne,就是删除全部符合的数据

高级查询

find

"""find"""
db.stu.find()   #查询所有的数据
db.stu.find({age:23})   #查询满足条件的数据
db.stu.findOne({age:23})    #查询满足条件的一个数据
db.stu.find().pretty()  #对数据进行美化

比较运算符

1. 等于:默认是等于判断,没有运算符
2. 小于:lt(less than)
3. 大于:gt(greater than)
4. 小于等于:lte(less than equal)
5. 大于等于:gte(greater than equal)
6. 不等于:$ne(not equal)

使用举例:

db.stu.find({age:{$le(18)}})    #查询年龄小于18的

范围运算符

1. in:在某个范围
2.nin:不在某个范围

用法举例:

db.stu.find({age:{$in[18,28,38]}})  #查询年龄为18或28或38的

逻辑运算符

and:直接写多个条件即可,例:db.stu.find({age:{gte:18},gender:true})
or:使用or,值为数组,数组中的每个元素为json,例:查询年龄大于18或性别为false的数据:db.stu.find({or:[{age:{gt:18}},{gender:{false}}]})

正则表达式

1. db.products.find({sku:/^abc/})   #查询以abc开头的sku
2. db.products.find({sku:{regex:"789"}})  #查询以789结尾的sku

limit和skip

1. db.stu.find().limit(2)   #查询两个学生的信息
2. db.stu.find().skip(2)    #跳过前两个学生的信息
3. db.stu.find().skip(2).limit(4)   #先跳过2个,再查找4个

自定义查询

db.stu.find({$where:function(){
    return this.age > 30;
}}) #查询年龄大于30的学生

投影

即返回满足条件的数据中的部分内容,而不是返回整条数据

db.stu.find({$where:function(){
    return this.age > 30, {name:1,hometown:1};
}}) #查询年龄大于30的学生,并返回其名字和hometown,其中,this是指从前到后的每一条数据;如果省略{name:xxx}就会返回该条数据的全部内容

db.stu.find({},{_id:0,name:1,hometown:1})   #显示所有数据的name和hometown,不显示_id,但是要注意,只有_id可以使用0;一般对其他字段来说,要显示的写1,不显示的不写即可,_id默认是会显示的

排序

db.stu.find().sort({age:-1})    #按年龄的降序排列,如果是{age:1}就是按按铃升序排序
db.stu.find().sort({age:1,gender:-1})   #按年龄的升序排列,如果年龄相同,按gender的降序排列

count方法

db.stu.find({条件}).count()   #查看满足条件的数据有多少条
db.stu.count({条件})

消除重复

db.stu.distinct("去重字段",{条件})
db.stu.distinct("hometown",{age:{$gt:18}})  #查看年龄大于18的人都来自哪几个地方

聚合aggregate

聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。
db.集合名称.aggregate({管道:{表达式}})

所谓管道,即把上一次的输出结果作为下一次的输入数据

常用管道如下

  • $group: 将集合中的⽂档分组, 可⽤于统计结果
  • $match: 过滤数据, 只输出符合条件的⽂档,match:匹配
  • $project: 修改输⼊⽂档的结构, 如重命名、 增加、 删除字段、 创建计算结果
  • $sort: 将输⼊⽂档排序后输出
  • $limit: 限制聚合管道返回的⽂档数
  • $skip: 跳过指定数量的⽂档, 并返回余下的⽂档
  • $unwind: 将数组类型的字段进⾏拆分,即展开的意思

表达式

语法:表达式:'$列名'
常⽤表达式:

  • sum: 计算总和,sum:1 表示以⼀倍计数
  • $avg: 计算平均值
  • $min: 获取最⼩值
  • $max: 获取最⼤值
  • $push: 在结果⽂档中插⼊值到⼀个数组中
  • $first: 根据资源⽂档的排序获取第⼀个⽂档数据
  • $last: 根据资源⽂档的排序获取最后⼀个⽂档数据

用法示例

"""group的使用"""
db.Temp.aggregate(
    {group:{_id:"gender"}}
)   #按性别分组
"""输出结果:
{ "_id" : 1 }
{ "_id" : 0 }
"""
db.Temp.aggregate( 
{group:{_id:"gender",count:{sum:1}}} 
)   #按性别分组并计数,sum:1是指每条数据作为1
"""输出结果如下:
{ "_id" : 1, "count" : 7 }
{ "_id" : 0, "count" : 1 }
"""
"""注意:_id和count的键不能变"""
db.Temp.aggregate(    {group:{_id:"gender",
    count:{sum:1},
    avg_age:{avg:"age"}}} 
)   #按年龄分组并计数,再分别计算其年龄的平均值
"""结果如下:
{ "_id" : 1, "count" : 7, "avg_age" : 22.857142857142858 }
{ "_id" : 0, "count" : 1, "avg_age" : 32 }
"""
"""注意:如果分组时_id:null,则会将整个文档作为一个分组"""



"""管道的使用"""
db.Temp.aggregate(
    {group:{_id:"gender",count:{sum:1},avg_age:{avg:"age"}}},
    {project:{gender:"_id",count:"count",avg_age:"avg_age"}}
)   #将group的输出再作为project的输入,因为前面已经有了_id,count,avg_age等输出键,所以在后面的管道中可以直接使用(此例中用了_id和avg_age),也可以使用1使其显示,0使其不显示
"""输出结果如下
{ "count" : 7, "gender" : 1, "avg_age" : 22.857142857142858 }
{ "count" : 1, "gender" : 0, "avg_age" : 32 }
"""


"""match管道的使用"""
#为什么使用match过滤而不是find的过滤?match可以将其数据交给下一个管道处理,而find不行
db.Temp.aggregate(
    {match:{age:{gt:20}}},
    {group:{_id:"gender",count:{sum:1}}},
    {project:{_id:0,gender:"_id",count:1}}
)   #先选择年龄大于20的数据;然后将其交给group管道处理,按照性别分组,对每组数据进行计数;然后再将其数据交给project处理,让_id字段显示为性别,不显示_id字段,显示count字段

"""sort管道的使用"""
db.Temp.aggregate(
    {group:{_id:"gender",count:{sum:1}}},
    {sort:{count:-1}}
)   #将第一个管道的数据按照其count字段的逆序排列,和find中的排序使用方式一样
"""结果如下:
{ "_id" : 1, "count" : 7 }
{ "_id" : 0, "count" : 1 }
"""

"""skip和limit的用法示例:
{limit:2}
{skip:5}
"""

"""unwind使用使例:"""
eg:假设某条数据的size字段为:['S','M','L'],要将其拆分
db.Temp.aggregate(
    {match:{size:["S","M","L"]}},  #先找到该数据
    {unwind:"$size"}
)
"""结果如下:
{ "_id" : ObjectId("5d57cada783675188ccd7ee0"), "size" : "S" }
{ "_id" : ObjectId("5d57cada783675188ccd7ee0"), "size" : "M" }
{ "_id" : ObjectId("5d57cada783675188ccd7ee0"), "size" : "L" }
"""


小练习:

"""数据和需求:
{ "country" : "china", "province" : "sh", "userid" : "a" }  
{  "country" : "china", "province" : "sh", "userid" : "b" }  
{  "country" : "china", "province" : "sh", "userid" : "a" }  
{  "country" : "china", "province" : "sh", "userid" : "c" }  
{  "country" : "china", "province" : "bj", "userid" : "da" }  
{  "country" : "china", "province" : "bj", "userid" : "fa" }  
需求:统计出每个country/province下的userid的数量(同一个userid只统计一次)
"""
db.Exci.aggregate(
    {group:{_id:{userid:"userid",province:"province",country:"country"}}},  #先按照三个字段分组(去重)
    {group:{_id:{country:"_id.country",province:"_id.province"},count:{sum:1}}},
    {project:{_id:0,country:"_id.country",province:"_id.province",count:"count"}}
)
"""注意:取字典里面的元素用(.)操作符;group的_id可以为字典"""
"""三个管道处理过后的数据分别如下:
#第一次group
{ "_id" : { "userid" : "a", "province" : "sh", "country" : "china" } }
{ "_id" : { "userid" : "b", "province" : "sh", "country" : "china" } }
{ "_id" : { "userid" : "c", "province" : "sh", "country" : "china" } }
{ "_id" : { "userid" : "da", "province" : "bj", "country" : "china" } }
{ "_id" : { "userid" : "fa", "province" : "bj", "country" : "china" } }

#第二次group
{ "_id" : { "country" : "china", "province" : "bj" }, "count" : 2 }
{ "_id" : { "country" : "china", "province" : "sh" }, "count" : 3 }

#最终结果
{ "country" : "china", "province" : "sh", "count" : 3 }
{ "country" : "china", "province" : "bj", "count" : 2 }
"""

"""也可以写成如下形式:"""
db.Temp.aggregate(
    {match:{size:["S","M","L"]}},
    {unwind:{path:"$size",preserveNullAndEmptyArrays:true}}
)   #path字段是要拆分的字段,参数表示保存Null和EmptyArrays,因为如果是原数据中有某字段为null或[],那么在拆分一个数据后,表中原来含nul或[]的那几条数据会消失

索引

作用:提升查询速度

db.t1.find({查询条件})
db.t1.find({查询条件}).explain('executionStats') 可以通过其中的"executionTimeMillisEstimate"字段查看查询所花费的时间

建立索引

  • 语法:db.集合.ensureIndex({属性1:1}, {unique:true}) 其中,1表示升序,-1表示降序,一般来说,升序或降序的影响不大;unique字段可以省略,加上后,保证索引唯一,即:如果我们用name作为索引,那么在集合中就不能有name值相同的数据;
  • 联合索引,即ensureIndex的参数为{属性1:1或-1, 属性2:-1或1},联合索引是为了保证数据的唯一性

  • 唯一索引的作用:比如,当我们爬取数据时,如果使用了唯一索引,那么,当我们爬到重复数据时,就不会存储到数据库中

  • 默认有一个index:_id

  • 查看索引:db.集合.getIndex()

  • 删除索引:db.集合.dropIndex("索引名称") #索引名称即我们创建索引时传入的字典{属性:1或-1},可以通过getIndex()查看时的key项

爬虫数据去重,实现增量式爬虫

  • 使用数据库建立唯一索引进行去重

  • url地址去重

    • 使用场景

      • 如果url对应的数据不会变,url地址能够唯一的判别一条数据的情况
    • 思路

      • url地址存在redis中
      • 拿到url地址,判断url地址在url的集合中是否存在
      • 存在:不再请求
      • 不存在:请求,并将该url地址存储到redis数据库中
    • 布隆过滤器

      • 使用加密算法加密url地址,得到多个值
      • 往对应值的位置把结果设置为1
      • 新来一个url地址,一样通过加密算法生成多个值
      • 如果对应位置的值全为1,说明这个url已经被抓过
      • 否则没有被抓过,就把对应位置的值设置为1
  • 根据数据本身去重

    • 选择特定字段,使用加密算法(md5,shal)将字段进行加密,生成字符串,存入redis集合中
    • 如果新来一条数据,同样的方法进行加密,如果得到的数据在redis中存在,说明数据存在,要么插入,要么更新,否则不存在,直接插入

数据的备份与恢复

备份的语法
mongodump -h dbhost -d dbname -o dbdirectory
-h: 服务器地址, 也可以指定端⼝号,如果是本机上,就可以省略
-d: 需要备份的数据库名称
-o: 备份的数据存放位置, 此⽬录中存放着备份出来的数据

备份的数据中,一个json和一个bson表示一个集合

恢复的语法
mongorestore -h dbhost -d dbname --dir dbdirectory
-h: 服务器地址
-d: 需要恢复的数据库实例,即数据库的名字
--dir: 备份数据所在位置


pymongo的使用

pip install pymongo
from pymongo import MongoClient

用法示例

from pymongo import MongoClient

client = MongoClient(host='127.0.0.1',port=27017)
#实例化client,即和数据库建立连接
collection = client['admin']['Temp']  #使用[]选择数据库和集合

collection.insert_one({"name":"laowang","age":33})  #插入一条数据
it_data = [{"name":"laoli","age":23},{"name":"laozhao","age":43}]
collection.insert_many(it_data) #插入多条数据

print(collection.find_one({"name":"laoli"}))
print(collection.find())    #是一个Cursor(游标)对象,一个Cursor对象只能进行一次遍历
for ret in collection.find():
    print(ret)  #遍历查看cursor对象
print(list(collection.find()))  #强制转化为list

collection.delete_one({"name":"laoli"}) #删除一个
collection.delete_many({"age":33})  #删除所有age为33的
# mongodb不需要我们手动断开连接

scrapy

scrapy简介

为什么要使用scrapy:使我们的爬虫更快更强

scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架

scrapy使用了Twisted异步网络框架,可以加快我们的下载

scrapy的工作流程

在这里插入图片描述

scheduler里面实际上存放的并不是url地址,而是request对象

在spiders处,url要先组装成request对象再交到scheduler调度器

scrapy引擎的作用:scheduler将request交给scrapy engine,engine再交给下载器,response也是先由下载器交给scrapy engine,然后再由engine交给spiders,url类似,先交给scrapy engine,再交给scheduler

engine实现了程序的解耦,response和request在经过scrapy后,还要经过各自的middleware,再交到目的地,因此我们就可以定义自己的中间件,对reponse和request进行一些额外的处理

爬虫中间件不会对爬虫提取的数据进行数据(实际上可以,但是因为有专门的部分进行这项工作,所以我们通常不这么做)

在这里插入图片描述

scrapy入门

  • 创建一 个scrapy项目:scrapy startproject 项目名(eg:myspider)

    在这里插入图片描述
  • 生成一个爬虫:scrapy genspider 爬虫名字 "允许爬取的域名"

  • 提取数据

    • 完善spider,使用xpath等方法
  • 保存数据

    • pipline中保存数据

scrapy具体流程及spider和pipline讲解

此处的项目名为mySpider,创建的爬虫名字为itcast

  • 新建一个python项目

  • 在Terminal中:scrapy startproject mySpider

  • 在Terminal中,根据提示:cd mySpider

  • 在Terminal中:scrapy genspider itcast "itcast.cn",此时,如果创建成功,就会在spiders目录中有了itcast.py;在里面,我们写上如下代码段内容:

    • 在项目文件夹中使用scrapy新建的爬虫都在spider文件夹中,每个spider文件即对应上面流程图中的spiders,其中有几个默认字段:

      • name:爬虫的名字,默认有
      • allowed_domains:默认有(在使用scrapy新建spider的时候通常会指定)
      • start_urls:默认有,但是通常需要我们自己修改,其值为我们最开始请求的url地址
      • parse方法:处理start_url对应的响应地址,通过yield将我们提取到的数据传递到pipline
    import scrapy
    
    
    class ItcastSpider(scrapy.Spider):
        name = 'itcast' #爬虫名
        allowed_domains = ['itcast.cn'] #允许爬取的范围
        start_urls = ['http://www.itcast.cn/channel/teacher.shtml']  #最开始请求的url地址
    
        def parse(self, response):
            """处理start_url对应的响应"""
            # ret1 = respnse.xpath("//div[@class='tea_con']//h3/text()").extract()
            # #提取数据
            # #extract()方法可以提取其中的文字
            # print(ret1)
            li_list = response.xpath("//div[@class='tea_con']//li")
            for li in li_list:
                item = {}
                item['name'] = li.xpath(".//h3/text()").extract_first() #提取第一个字符串
                #使用extract_first,如果是没有拿到元素,会返回none,而不是报错(extract()[0]在拿不到元素的情况下会报错)
                item['title'] = li.xpath(".//h4/text()").extract_first()
                yield item  #将item传给piplines
    
  • 在Terminal中,进入项目文件夹下:scrapy crawl itcast,就会自动开始爬取;然后在terminal中输出一些结果和一些日志,我们可以在settings.py中对日志的级别进行设置,比如添加:LOG_LEVEL = "WARNING",比warning等级小的日志都不会输出

  • pipline的使用

    • pipline对应流程图中的item pipline

    • 要使用pipline,需要在项目的settings.py文件中取消对pipline的注释,使其可用,即

      # Configure item pipelines
      # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
      ITEM_PIPELINES = {
         'mySpider.pipelines.MyspiderPipeline': 300,
         #意为:mySpider项目下的piplines模块中的MyspiderPipline类,后面的数字(300)表示距离引擎的远近,越近,数据就会越先经过该pipline
         #所谓管道,就是把前面管道处理后的数据再交给后面的管道处理,这里ITEM_PIPLINES的格式为字典:管道类的位置:距离引擎的远近
         #把spiders提取的数据由近及远交给各个管道处理
         #亦即:我们可以在piplines中定义自己的多个管道,然后在这里进行注册,使其可用,如下
         'mySpider.pipelines.MyspiderPipeline1': 301,
      }
      
  • 执行爬虫爬取数据:scrapy crawl 爬虫的名字(类下面的name的值)

  • 在piplines.py中,我们定义了如下的两个管道

    • 为了让数据能够在各个管道间进行传递,每个管道必须return item,这里的item即为spider中传递过来的数据
    • process_item方法是必须的,专门用于对数据的处理,只有它可以接受传递过来的item
    • spider就是爬虫在传item的时候,把自己也传递过来了,即:这里的参数spider就是我们在spiders目录下的爬虫名的py文件中定义的spider类
    class MyspiderPipeline(object):
        def process_item(self, item, spider):
            print(item)
            return item
    
    class MyspiderPipeline1(object):
        def process_item(self, item, spider):
            print(item.items())
            return item
    
  • 为什么需要有多个pipline

    • 一个项目通常有多个爬虫,而对于爬取的数据,我们通常要进行的处理不相同,因此就需要使用不同的pipline
      • 此时需要对传过来的item进行判别,比如可以使用在item中添加某字段以判别(或者使用spider进行判别,比如:if spider.name == xxx),如果是我们要处理的数据才进行处理,否则传递给其他pipline
    • 一个spider的内容可能要做不同的操作,比如存入不同的数据库中,我们就可以使用多个pipline分多步进行

logging模块的使用

在settings.py文件中,可以添加字段:LOG_LEVEL="log等级",以控制当前爬虫的输出的log等级(大于)

在spider中输出log的两种常用方法:

  • import logging,然后使用logging.warning(要输出的信息),无法显示log来自哪个文件

  • impot logging,然后logger = logging.getLogger(__name__),使用logger.warning(要输出的数据),此种方法可以输出日志来自哪个文件

    • ps:我们实例化了一个logger之后,在其他的文件中如果要使用log,不必单独再去实例化一个对象,直接导入现有的logger即可

如果我们要想使log输出到文件中,而非terminal,则需要在settings.py中添加字段:LOG_FILE = "保存log的文件路径"

如果要自定义log的格式,在使用logging前:logging.basicConfig(xxx),其中的xxx即我们要自定义的log格式
logging.basicConfig()示例:

import logging
 
logging.basicConfig(level=logging.DEBUG,
                format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                datefmt='%a, %d %b %Y %H:%M:%S',
                filemode='w')
"""basicConfig参数"""
logging.basicConfig函数各参数:
filename: 指定日志文件名
filemode: 和file函数意义相同,指定日志文件的打开模式,'w'或'a'
format: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:
 %(levelno)s: 打印日志级别的数值
 %(levelname)s: 打印日志级别名称
 %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
 %(filename)s: 打印当前执行程序名
 %(funcName)s: 打印日志的当前函数
 %(lineno)d: 打印日志的当前行号
 %(asctime)s: 打印日志的时间
 %(thread)d: 打印线程ID
 %(threadName)s: 打印线程名称
 %(process)d: 打印进程ID
 %(message)s: 打印日志信息
datefmt: 指定时间格式,同time.strftime()
level: 设置日志级别,默认为logging.WARNING
stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略

翻页请求

  • 在爬虫中,首先,获得下一页的url:next_url = response.xpath("//a[text()='下一页']/@href").extract()以获得下一页的url
  • 然后使用scrapy.Request构造一个request,同时指定回调函数
  • 在回调函数中,对于要pipline处理的数据,同样要yield
    while next_url:
        yield scrapy.Request(next_url, callback=self.parse)
        #如果下一页数据的处理和当前页相同,那么回调函数就直接指定当前函数即可,如果处理方式,则另外定义一个函数进行回调即可;这里实际上也是实例化了一个request对象交给引擎
    

scrapy.request的其他知识点

在这里插入图片描述
  • 如图所示,在crapy中,cookies就不能再放到headers中去
  • 所谓解析函数,可以简单理解为回调函数,meta的格式为字典,在解析函数中获取该数据时:response.meta["键"]

设置user-agent

  • user-agent的使用:在项目的设置文件中找到对应项进行设置即可

案例(结合下面的item)

爬取阳光热线问政平台:

# -*- coding: utf-8 -*-
import scrapy
from yangguang.items import YangguangItem

class YgSpider(scrapy.Spider):
    name = 'yg'
    allowed_domains = ['sun0769.com']
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=0']

    def parse(self, response):
        tr_list = response.xpath("//div[@class='greyframe']/table[2]/tr/td/table/tr")
        for tr in tr_list:
            item = YangguangItem()
            item['title'] = tr.xpath("./td[2]/a[@class='news14']/@title").extract_first()
            item['href'] = tr.xpath("./td[2]/a[@class='news14']/@href").extract_first()
            item['publish_data'] = tr.xpath("./td[last()]/text()").extract_first()

            yield scrapy.Request(
                item['href'],
                callback= self.parse_detail,
                meta={"item":item}
            )
            next_url = response.xpath("//a[text()='>']/@href").extract_first()
            if next_url:
                yield scrapy.Request(
                    next_url,
                    callback=self.parse
                )


    def parse_detail(self,response):
        """处理详情页"""
        item = response.meta['item']
        item['content'] = response.xpath("//td[@class='txt16_3']//text()").extract_first()
        item['content_img'] = response.xpath("//td[@class='txt16_3']//img/@src").extract()
        yield  item

scrapy深入

Items

使用流程:

  • 先在items.py中写好我们要使用的字段,如图:

    在这里插入图片描述
    • 说明:就相当于我们定义了一个字典类,规定好了里面可以有的键,以后如果使用这个字典的实例时,发现想往里面存入未定义的键,程序就会报错
  • 使用时,在spider中导入该类(这里是Myspideritem),然后用其实例化一个对象(当作字典使用即可)

  • 然后通常是用这个字典存储我们提取的数据,然后把其在各个piplines间传递

    在这里插入图片描述
  • 注意:item对象不能直接插入mongodb(只是像字典,毕竟不是字典),可以强制将其转化为字典,然后存入即可

  • ps:对不同爬虫爬取的数据,我们可以定义多个item类,然后在pipline中处理时,可以用isinstance判断是否为某个item类的实例,如果是,我们才处理

debug信息

在这里插入图片描述

scrapy shell

Scrapy shell是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPath表达式

使用方法:
scrapy shell http://www.itcast.cn/channel/teacher.shtml

就会自动进入shell,在shell中进行一些操作,会自动提示
然后就能得到response

response:

  • response.url:当前响应的url地址
  • response.request.url:当前响应对应的请求的url地址
  • response.headers:响应头
  • response.body:响应体,也就是html代码,默认是byte类型
  • response.requests.headers:当前响应的请求头
  • response.xpath()

spider:

  • spider.name
  • spider.log(log信息)

settings

settings中的字段

默认已有字段:

  • bot_name:项目名
  • spider_modules:爬虫位置
  • newspider_module:新建爬虫的位置
  • user-agent:用户代理
  • robotstxt_obey:是否遵守robot协议
  • CONCURRENT_REQUESTS:并发请求的最大数量
  • DOWNLOAD_DELAY:下载延迟
  • CONCURRENT_REQUESTS_PER_DOMAIN:每个域名的最大并发请求数
  • CONCURRENT_REQUESTS_PER_IP:每个代理ip的最大并发请求数
  • COOKIES_ENABLED:是否开启cookies
  • TELNETCONSOLE_ENABLED:是否启用teleconsole插件
  • DEFAULT_REQUEST_HEADERS:默认请求头
  • spider_midddleware:爬虫中间件
  • downlowd_middleware:下载中间件
  • EXTENSIONS:插件
  • ITEM_PIPELINES:管道,其格式为:管道的位置:权重
  • AUTOTHROTTLE_ENABLED:自动限速
  • 缓存的配置项

可自己添加字段:

  • LOG_LEVEL

在其他位置中要使用配置中的数据:

  1. 法一:直接导入settings模块使用
  2. 如果是在spider中:可以直接用self.settings.get()或是self.settings[]以字典的形式存取相关数据
  3. 如果是在pipline中,由于传过来了spider,就以spider.settings.get()或spider.settings[]存取

piplines

import json
class myPipline(object):
    def open_spider(self,spider):
    """在爬虫开启的时候执行一次"""
    #比如实例化MongoClient
        pass

    def close_spider(self,spider):
    """在爬虫关闭的时候执行一次"""
        pass

    def process_item(self,item,spider):
    """对spider yield过来的数据进行处理"""
        pass
        return item
        """如果不return item,其他的pipline就无法获得该数据"""

CrawlSpider

之前爬虫的思路:
1、从response中提取所有的a标签对应的url地址
2、自动的构造自己requests请求,发送给引擎

改进:

满足某个条件的url地址,我们才发送给引擎,同时能够指定callback函数

如何生成crawlspider:
eg:
scrapy genspider –t crawl csdn “csdn.com"

crawlspider示例:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
import re

class CfSpider(CrawlSpider):    #继承的父类不再是scrapy.spider
    name = 'cf'
    allowed_domains = ['circ.gov.cn']
    start_urls = ['http://circ.gov.cn/web/site0/tab5240/module14430/page1.htm']

    """定义提取url规则的地方"""
    """每个Rule是一个元组"""
    """注意:每个url提取出来后被构造成一个请求,他们没有先后顺序"""
    rules = (
        #Rule是一个类,LinkExtractor: 链接提取器,其参数是一个正则表达式,提取到了link,就交给parse函数进行请求
        #所以我们在crawlspider中不能自己定义parse函数
        #url请求的响应数据会交给callback处理,如果不用提取该url中的数据,就可以不指定callback
        #follow,当前url地址的相应是否重新进入rules来提取url地址(会挨个按规则提取,如果被前面的正则表达式匹配到了,就不会再被后面的进行匹配提取,所以写正则表达式的时候应该尽可能明确)
        #注意:crawlspider能够帮助我们自动把链接补充完整,所以我们下面的allow中并没有手动补全链接
        Rule(LinkExtractor(allow=r'/web/site0/tab5240/infod+.htm'), callback='parse_item', follow=False),
        Rule(LinkExtractor(allow=r'/web/site0/tab5240/module14430/paged+.htm'), follow=True),
    )
    """parse函数不见了,因为其有特殊功能,不能定义"""
    def parse_item(self, response):
        """解析函数"""
        item = {}
        #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
        #item['name'] = response.xpath('//div[@id="name"]').get()
        #item['description'] = response.xpath('//div[@id="description"]').get()
        item['title'] = re.findall(r"(.*?)",response.body.decode())[0]
        item['date'] = re.findall(r"发布时间:(d{4}-d{2}-d{2})",response.body.decode())
        print(item)
    #   也可以自己再yiel scrapy.Request
    #     yield scrapy.Request(
    #         url,
    #         callback=self.parse_detail,
    #         meta={"item":item}
    #     )
    #
    # def parse_detail(self,response):
    #     item = response.meta['item']
    #     pass
    #     yield item

LinkExtractor和Rule的更多知识点

在这里插入图片描述

中间件

下载中间件

下载中间件是我们要经常操作的一个中间件,因为我们常常需要在下载中间件中对请求和响应做一些自定义的处理

如果我们要使用中间件,需要在settings中开启,其格式也是:位置:权重(或者说是距离引擎的位置,越小越先经过)

Downloader Middlewares默认的方法

  • process_request(self, request, spider):
    当每个request通过下载中间件时,该方法被调用。
  • process_response(self, request, response, spider):
    当下载器完成http请求,传递响应给引擎的时候调用

案例:使用随机user-agent:

在settings.py中定义USER_AGENTS_LIST:

USER_AGENTS_LIST = ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
                    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0',
                    ]

在middlewares.py中定义下载中间件:

import random
class RandomUserAgentMiddleware:
    """自定义一个下载中间件"""
    def process_request(self,request,spider):
        ua = random.choice(spider.settings.get("USER_AGENTS_LIST"))
        request.headers["User-Agent"] = ua
        #request.meta['proxy'] = "你的proxy"  #也可以通过此种方法来使用代理
        # return request,process不能返回request

class CheckUserAgentMiddleware:
    def process_response(selfs,request,response,spider):
        print(request.headers["User-Agent"])    #查看是否实现了随机用户代理
        return response
        # process_response必须返回reponse

并在settings.py中对自己的中间件进行注册:

DOWNLOADER_MIDDLEWARES = {
   # 'circ.middlewares.CircDownloaderMiddleware': 543,
    'circ.middlewares.RandomUserAgentMiddleware': 544,
    'circ.middlewares.CheckUserAgentMiddleware': 545,   #这里的权重并不重要,因为他们一个是处理请求,一个是处理响应的
}

spiders内容省略

scrapy模拟登录

对于scrapy来说,有两个方法模拟登陆:
1、直接携带cookie
2、找到发送post请求的url地址,带上信息,发送请求

直接携带cookie

案例:爬取人人网登录后的信息

import scrapy
import re

class RenrenSpider(scrapy.Spider):
    name = 'renren'
    allowed_domains = ['renren.com']
    start_urls = ['http://www.renren.com/971962231/profile']    #人人网的个人主页

    def start_requests(self):
        """覆盖父类的start_request方法,从而使其携带我们自己的cookies"""
        cookies = "我的cookies"
        cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split(";")}
        yield scrapy.Request(
            self.start_urls[0],
            callback=self.parse,    #表示当前请求的响应会发送到parse
            cookies=cookies
        )   #因为cookies默认是enable的,所以下次请求会自动拿到上次的cookies

    def parse(self, response):
        print(re.findall(r"假装",response.body.decode())) #验证是否请求成功
        yield scrapy.Request(
            "http://www.renren.com/971962231/profile?v=info_timeline",
            callback=self.parse_data,
        )

    def parse_data(self,response):
        """"访问个人资料页,验证cookie的传递"""
        print(re.findall(r"学校信息",response.body.decode()))

此外,为了查看cookies的传递过程,可以在settings中加上字段:COOKIES_DEBUG = True

scrapy发送post请求

案例:爬取github

spider:

import scrapy
import re

class GitSpider(scrapy.Spider):
    name = 'git'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/login']

    def parse(self, response):
        authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first()
        utf8 = response.xpath("//input[@name='utf8']/@value").extract_first()
        webauthn_support = response.xpath("//input[@name='webauthn-support']/@value").extract_first()
        post_data = {
            "login":"你的邮箱",
            "password":"密码",
            "webauthn-support":webauthn_support,
            "authenticity_token":authenticity_token,
            "utf8":utf8
        }
        print(post_data)
        yield scrapy.FormRequest(
            "https://github.com/session",   #数据提交到的地址
            formdata=post_data,
            callback=self.after_login
            #无论这个post请求成功没有,响应都会交给after_login
        )

    def after_login(self,response):
        print(response)
        print(re.findall(r"Trial_Repo", response.body.decode()))    #匹配我的某个仓库名,以验证是否成功

settings中的修改字段:

USER_AGENT = 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36'
ROBOTSTXT_OBEY = False

scrapy还可以帮我们自动从表单中提取action的地址

爬取github登录后的页面:
spider:

# -*- coding: utf-8 -*-
import scrapy
import re

class Github2Spider(scrapy.Spider):
    name = 'github2'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/login']

    def parse(self, response):
        post_data = {
            "login":"2537119279@qq.com",
            "password":"A1d9b961017#"
        }
        yield scrapy.FormRequest.from_response(
            #自动从response中寻找form表单,然后将formdata提交到对应的action的url地址
            #如果有多个form表单,可以通过其他参数对表单进行定位,其他参数见源码
            response,
            formdata=post_data,
            callback=self.after_login,
        )

    def after_login(self,response):
        print(re.findall(r"Trial_Repo", response.body.decode()))

另:无需在settings中设置任何东西


scrapy_redis

  • scrapy_redis的作用:reqeust去重,爬虫持久化,和轻松实现分布式

scrapy_redis爬虫的流程

在这里插入图片描述

上图中的指纹是指request指纹,指纹能够唯一标识一个request,从而避免重复的reqeust请求

所以redis中常存储待爬的request对象和已爬取的request的指纹

redis基础

在这里插入图片描述

scrapy_redis的地址:https://github.com/rmax/scrapy-redis

推荐阅读更多精彩内容

  • 来自一位资深Python大佬对爬虫的总结
    由于某些原因最近终于可以从工作的琐事中抽出身来,有时间把之前的一些爬虫知识进行了一个简单的梳理,也从中体会到阶段性...
    小姐姐吖_6271阅读 104评论 0赞 2
  • Selenium:反爬
    1. 反爬 有时候,我们利用 Selenium 自动化爬取某些网站时,极有可能会遭遇反爬。 实际上,我们使用默认的...
    小啊小狼阅读 978评论 0赞 7
  • (Python版) Scrapy+Django+Selenium 爬取Boss直聘 职位信息
    絮叨一下(本言论参考其他作者) boos直聘,想必对于找工作的同志都非常熟悉,为了快速获取boss上的发布职位信息...
    琴伴一生阅读 161评论 0赞 1
  • python接口请求之requests详解
    一、requests 1.1、简介 Requests 是用 Python 语言编写, 基于 urllib,采用 A...
    小啊小狼阅读 148评论 0赞 4
  • Python批量爬取代理ip,并自动插入到Excel表格!
    思路: 以“http://www.66ip.cn/”网址为例,使用requests访问,通过xpath解析相关标签...
    爱是一道光_e5f7阅读 366评论 0赞 8
评论0
1赞
赞赏
下载App

{"dataManager":"[]","props":{"isServer":true,"initialState":{"global":{"done":false,"artFromType":null,"fontType":"black","modal":{"ContributeModal":false,"RewardListModal":false,"PayModal":false,"CollectionModal":false,"LikeListModal":false,"ReportModal":false,"QRCodeShareModal":false,"BookCatalogModal":false,"RewardModal":false},"ua":{"value":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36","isIE11":false,"earlyIE":null,"chrome":"58.0","firefox":null,"safari":null,"isMac":false},"$diamondRate":{"displayable":false,"rate":0},"readMode":"day","locale":"zh-CN","seoList":[{"comments_count":0,"public_abbr":"由于某些原因最近终于可以从工作的琐事中抽出身来,有时间把之前的一些爬虫知识进行了一个简单的梳理,也从中体会到阶段性...","share_image_url":"https://upload-images.jianshu.io/upload_images/25361621-ddab641d5d9712c6.jpg","slug":"1f7bbbf5b6dd","user":{"id":25361621,"nickname":"小姐姐吖6271","slug":"9eeaa0a7d671","avatar":"https://upload.jianshu.io/users/upload_avatars/25361621/0575042f-0b60-46f0-a428-6642047b9656"},"likes_count":2,"title":"来自一位资深Python大佬对爬虫的总结","id":82538560,"views_count":104},{"comments_count":0,"public_abbr":"1. 反爬 有时候,我们利用 Selenium 自动化爬取某些网站时,极有可能会遭遇反爬。 实际上,我们使用默认的...","share_image_url":"https://upload-images.jianshu.io/upload_images/12561298-add3544d551553ac.png","slug":"7ddac046157d","user":{"id":12561298,"nickname":"小啊小狼","slug":"4e23be34d51d","avatar":"https://upload.jianshu.io/users/upload_avatars/12561298/f546503e-7740-492e-8e91-7a1c25cb2427"},"likes_count":7,"title":"Selenium:反爬","id":77894419,"views_count":978},{"comments_count":0,"public_abbr":"絮叨一下(本言论参考其他作者) boos直聘,想必对于找工作的同志都非常熟悉,为了快速获取boss上的发布职位信息...","share_image_url":"https://upload-images.jianshu.io/upload_images/25125379-f4d7fa1b57d79f38.png","slug":"2b58831d269d","user":{"id":25125379,"nickname":"琴伴一生","slug":"26aae85fa85e","avatar":"https://upload.jianshu.io/users/upload_avatars/25125379/afd249f8-96e9-4d64-b049-ddeda9a5bd0a"},"likes_count":1,"title":"(Python版) Scrapy+Django+Selenium 爬取Boss直聘 职位信息","id":79529841,"views_count":161},{"comments_count":0,"public_abbr":"一、requests 1.1、简介 Requests 是用 Python 语言编写, 基于 urllib,采用 A...","share_image_url":"https://upload-images.jianshu.io/upload_images/12561298-85c515901f076900.png","slug":"a555005db5b3","user":{"id":12561298,"nickname":"小啊小狼","slug":"4e23be34d51d","avatar":"https://upload.jianshu.io/users/upload_avatars/12561298/f546503e-7740-492e-8e91-7a1c25cb2427"},"likes_count":4,"title":"python接口请求之requests详解","id":81589561,"views_count":148},{"comments_count":0,"public_abbr":"思路: 以“http://www.66ip.cn/”网址为例,使用requests访问,通过xpath解析相关标签...","share_image_url":"https://upload-images.jianshu.io/upload_images/24596166-e7a74b6176b089fe","slug":"5713105d84f7","user":{"id":24596166,"nickname":"爱是一道光_e5f7","slug":"7e41657d7c21","avatar":"https://upload.jianshu.io/users/upload_avatars/24596166/dc6d513e-c787-446a-9fad-7ebd19480c79"},"likes_count":8,"title":"Python批量爬取代理ip,并自动插入到Excel表格!","id":79259334,"views_count":366}]},"note":{"data":{"is_author":false,"last_updated_at":1569718718,"public_title":"Python67-爬虫","purchased":false,"liked_note":false,"comments_count":0,"free_content":"u003ch1u003e爬虫的基础知识u003c/h1u003enu003cpu003eu003cstrongu003e爬虫的定义u003c/strongu003eu003c/pu003enu003cpu003e只要是浏览器可以做的事情,原则上,爬虫都可以帮助我们做,即:浏览器不能够做到的,爬虫也不能做u003c/pu003enu003cpu003e网络爬虫:又叫网络蜘蛛(spider),网络机器人,就是模拟客户端发送网络请求,接受请求响应,一种按照一定的规则,自动地抓取互联网信息的程序u003c/pu003enu003cpu003eu003cstrongu003e爬虫的分类u003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e通用爬虫u003c/pu003enu003cpu003e通常指搜索引擎的爬虫(面对整个互联网)u003c/pu003enu003c/liu003enu003cliu003enu003cpu003e聚焦爬虫:u003c/pu003enu003cpu003e针对特定网站的爬虫u003c/pu003enu003c/liu003enu003cliu003enu003cpu003e流程:u003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 634px; max-height: 389px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 61.36000000000001%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="634" data-height="389"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-2cc3116a74817200" data-original-width="634" data-original-height="389" data-original-format="image/png" data-original-filesize="55360"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e爬虫的工作流程u003c/divu003enu003c/divu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003eROBOTS协议u003c/strongu003eu003c/pu003enu003cpu003e网站通过robots协议告诉搜索引擎那些页面可以抓取,那些页面不能抓取u003c/pu003enu003cpu003e例如:u003ca href="https://links.jianshu.com/go?to=https%3A%2F%2Fwww.taobao.com%2Frobots.txt" target="_blank"u003ehttps://www.taobao.com/robots.txtu003c/au003e(通常是网站后面加/robots.txt即可以看到,就是一个文本文件)u003c/pu003enu003cpu003e部分内容如下:u003c/pu003enu003cpreu003eu003ccode class="python"u003eUser-agent: Baiduspider #用户代理,可以理解为浏览器的身份标识,通过这个字段可以告诉服务器是什么样的程序在请求网站,Baiduspider即百度的搜索引擎nAllow: /article #表示允许爬的内容nAllow: /oshtmlnAllow: /ershounAllow: /$nDisallow: /product/ #表示不允许该用户代理爬的内容nDisallow: /nu003c/codeu003eu003c/preu003enu003cpu003e但是robots只是道德层面的约束u003c/pu003enu003cpu003eu003cstrongu003ehttp和httpsu003c/strongu003eu003c/pu003enu003cpu003e为了拿到和浏览器一模一样的数据,就必须要知道http和httpsu003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 442px; max-height: 452px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 102.25999999999999%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="442" data-height="452"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-cb12616d2d6ea10e" data-original-width="442" data-original-height="452" data-original-format="image/png" data-original-filesize="30792"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003culu003enu003cliu003enu003cpu003ehttp:u003c/pu003enu003cpu003e超文本传输协议,明文方式传输,默认端口80u003c/pu003enu003c/liu003enu003cliu003enu003cpu003ehttps:u003c/pu003enu003cpu003ehttp+ssl(安全套接字层),会对数据进行加密,默认端口443u003c/pu003enu003c/liu003enu003c/ulu003enu003cpu003ehttps更安全,但是性能更低(耗时更长)u003c/pu003enu003cpu003eu003cstrongu003e浏览器发送http请求的过程u003c/strongu003eu003c/pu003enu003cpu003eu003c/pu003eu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 320px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 42.9%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="746" data-height="320"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-7319e8dc17c85505" data-original-width="746" data-original-height="320" data-original-format="image/png" data-original-filesize="106503"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enps:爬虫在爬取数据的时候,不会主动的请求css、图片、js等资源,就算自己爬取了js的内容,也只是字符串,而不会执行,故,u003cstrongu003e浏览器渲染出来的内容和爬虫请求的页面并不一样u003c/strongu003eu003cpu003eu003c/pu003enu003cpu003e爬虫要根据当前url地址对应的响应为准,当前url地址的elements的内容和url的响应不一样,u003ccodeu003e特别要注意tbody,经常在elements中有而响应中无u003c/codeu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e页面上的数据在哪里?u003c/pu003enu003culu003enu003cliu003e当前url地址对应的相应中u003c/liu003enu003cliu003e其他url地址对应的相应中,如ajax请求u003c/liu003enu003cliu003ejs生成:1、部分数据在响应中,2、全部由js生成u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003eurl的格式u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003ehost:服务器的ip地址或者是域名nport:服务器的端口npath:访问资源的路径nquery-string:参数,发送给http服务器的数据nanchor:锚点(跳转到网页的制定锚点位置,anchor也有主播的意思)nu003c/codeu003eu003c/preu003enu003cpu003e例:u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fitem.jd.com%2F11936238.html%23product-detail" target="_blank"u003ehttp://item.jd.com/11936238.html#product-detailu003c/au003e,就是一个带锚点的url,会自动跳转到商品详情,但是要注意,一个页面带锚点和不带锚点的响应是一样的(写爬虫的时候,就可以直接删掉锚点的部分)u003c/pu003enu003cpu003eu003cstrongu003ehttp请求格式u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 621px; max-height: 220px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 35.43%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="621" data-height="220"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-5f2bbdde75125a6b" data-original-width="621" data-original-height="220" data-original-format="image/png" data-original-filesize="58827"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cpu003e如,在访问百度时,查看request headers的source时,就可以看到如下内容u003c/pu003ennu003cpreu003eu003ccode class="python"u003eGET http://www.baidu.com/ HTTP/1.1 n#请求方法:get;url:http:xxxx.com/;协议版本:http1.1,然后换行nHost: www.baidu.comn#请求头部:host;值:www.baidu.com;换行,以下类似nProxy-Connection: keep-alive #keep-alive表示支持长链接。为什么要用长连接:不用频繁握手挥手,提高效率nUpgrade-Insecure-Requests: 1 #升级不安全的请求:把http请求转换为https的请求nDNT: 1 #Do not tracknUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 #浏览器的标识。名字/版本号。如果有模拟手机版的请求,改user agent即可,不同的user agent访问相同的url,可能会得到不同的内容nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 #浏览器告诉服务器自己可以接受什么样的数据nReferer: http://baidu.com/nAccept-Encoding: gzip, deflate #告诉服务器自己可以接受什么样的压缩方式nAccept-Language: en-US,en;q=0.9 #告诉服务器自己可以接受什么样语言的数据,q:权重,更愿意接受哪种语言nCookie: BAIDUID=B8BE58B25611B7BBA38ECFE9CE75841F:FG=1; BIDUPSID=B8BE58B25611B7BBA38ECFE9CE75841F; PSTM=1565080210; BD_UPN=12314753; delPer=0; BD_HOME=0; H_PS_PSSID=26522_1453_21118_29523_29521_29098_29568_28830_29221_26350_22159; BD_CK_SAM=1; PSINO=7; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; COOKIE_SESSION=218_0_3_0_0_7_1_1_1_3_76_2_0_0_0_0_0_0_1565599571%7C3%230_0_1565599571%7C1; rsv_jmp_slow=1565599826516; H_PS_645EC=2c80At1Is237xdMOfC3ju2q0qlWJ%2FFlbD5N50IQeTrCHyIEsZN6yQYBgLHI; B64_BOT=1 #cookie:保存用户的个人信息。ps:cookie和session的区别:cookie保存在浏览器本地,不安全,存储量有上限,session保存在服务器,更安全,往往没有上限。cookie又分为request cookie和reponse cookie,在浏览器中可以查看nu003c/codeu003eu003c/preu003enu003cpu003e除了以上字段,可能还有referer字段,表示当前url是从哪个url过来的;x-request-with字段,表示是ajax异步请求u003c/pu003enu003cpu003e以上字段中,主要是user agent(模拟浏览器),cookie(反反爬虫)u003c/pu003enu003cpu003eu003cstrongu003e常见的请求方式u003c/strongu003eu003c/pu003enu003culu003enu003cliu003eu003cpu003eget:除了post,基本都用get,更常用u003c/pu003eu003c/liu003enu003cliu003eu003cpu003epost:常用于提交表单的时候(安全),传输大文件的时候(美观)u003c/pu003eu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e响应状态码(status code)u003c/strongu003eu003c/pu003enu003culu003enu003cliu003e200:成功u003c/liu003enu003cliu003e302/307:临时转移至新的urlu003c/liu003enu003cliu003e404:not foundu003c/liu003enu003cliu003e500:服务器内部错误u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e字符串知识复习u003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003estr类型和bytes类型u003c/pu003enu003culu003enu003cliu003ebytes:二进制类型,互联网上数据都是以二进制的方式传输的u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003eu003cpu003estr:unicode的呈现形式u003c/pu003eu003c/liu003enu003c/ulu003enu003cpu003eps:ascii码是一个字节,unicode编码通常是2个字节,utf-8是unicode的实现方式之一,是一变长的编码方式,可以是1、2、3个字节u003c/pu003enu003cpu003eu003ccodeu003e编码和解码的方式必须一致,否则会乱码u003c/codeu003eu003c/pu003enu003cpu003eu003cstrongu003e爬虫部分重要的是理解,而不是记忆u003c/strongu003eu003c/pu003enu003chru003enu003ch1u003eRequest模块使用入门u003c/h1u003enu003cpu003eQ:为什么要学习requests,而不是urllib?u003c/pu003enu003colu003enu003cliu003erequests的底层实现是就urllib,urllib能做的事情,requests都可以做;u003c/liu003enu003cliu003erequests在python2和python3中通用,方法完全一样;u003c/liu003enu003cliu003erequests简单易用;u003c/liu003enu003cliu003erequest能够自动帮我们解压(gzip等)网页内容u003c/liu003enu003c/olu003enu003cpu003e中文文档api:u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fdocs.python-requests.org%2Fzh_CN%2Flatest%2Findex.html" target="_blank"u003ehttp://docs.python-requests.org/zh_CN/latest/index.htmlu003c/au003eu003c/pu003enu003cpu003eu003cstrongu003e基础使用u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e"""基础入门"""nimport requestsnnr = requests.get('http://www.baidu.com') #r即为获得的响应。所请求的所有东西都在r中n# 必须要包含协议(http或https);还有dlelete,post等方法nnprint(r)nnprint(r.text) #text是一个属性,其实可以通过他的意思判断,text是一个名字,所以是属性,如果是方法,常为动词n#会自动根据推测的编码方式解码为strnnprint(r.encoding) #根据头部推测编码方式,如果猜错了,我们解码的时候就必须自己指定解码的方式nnprint(r.content) #也是属性,是一个bytes类型的数据。nprint(r.content.decode()) #将bytes类型转换为str类型。默认的解码方式为utf-8nprint(r.status_code) #获取状态码nassert r.status_code 200 #断言状态码为200,如果断言失败,会报错:assertionerrorn#可以用此方法判断请求是否成功nnprint(r.headers) #响应头,我们主要关注其中的set-cookie(在本地设置cookie)字段nprint(r.request) #关于对应相应的请求部分,是一个对象nprint(r.request.url) #请求的url地址nprint(r.url) #响应地址,请求的url地址和响应的url地址可能会不一样(因为可能和重定向)nprint(r.request.headers) #请求头,如果不设置,默认的user-agent是python-requests/x.xx.xnnwith open('baidu_r.txt','w') as f: #测试:查看默认的user-agent访问时返回的内容n f.write(r.content.decode())nn"""nrequests中解编码的方法:n1. r.content.decode() #content的类型为bytes,必须再次解码n2. r.content.decode('gbk')n3. r.text #text是按照推测的编码方式进行解码后的数据,他的类型为strn"""nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e发送带header的请求u003c/strongu003eu003c/pu003enu003cpu003e具体的header到浏览器中进行查看u003c/pu003enu003cpreu003eu003ccode class="python"u003e"""n为什么请求需要带上header?n 模拟浏览器,欺骗服务器,获取和浏览器一致的内容nnheader的形式:字典,形式:{request headers冒号前面的值:request headers冒号后面的值},大部分情况,我们带上user-agent即可,少数情况需要cookien用法:requests.get(url,headers=headers) n"""nnimport requestsnnheaders = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}nnresponse = requests.get('https://www.baidu.com',headers=headers)nnprint(response.content.decode()) #会发现响应中的数据比不带header多许多nnu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e发送带参数的请求u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e#在url中带参数的形式n#例如:在我们百度搜索某东西时,就会带上一大堆参数,但是大部分可能是没有用的,我们可以尝试删除,然后我们在爬虫中带的参数只需要为其中不能删除的部分即可n"""n参数的形式:字典nkw={'wd':'长城'}n用法:requests.get(url,params=kw)n"""nimport requestsnnheaders = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}nnparams = {'wd':'这里是中文'}n#如果参数含中文,其就会被自动编码,编码的后的形式含百分号,我们可以使用url解码来查看原来的内容nnr = requests.get('https://www.baidu.com',params=params,headers=headers)nprint(r.status_code)nprint(r.request.url)nprint(r.url)nprint(r.content.decode())nn#当然,我们也可以直接把参数拼接到url中去,而不单独传参(也不需要手动编码),eg:r = requests.get('https://www.baidu.com/s?wd={}'.formate('传智播客'))nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e小练习:爬贴吧前1000页u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003eimport requestsnkw = input('请输入您要爬取的贴吧名:')nurl = 'https://tieba.baidu.com/f?kw=%{kw}8u0026amp;pn='.format(kw=kw)nheaders = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}nnnfor i in range(1000):n url = urlr.formate(str(i*50))n r = requests.get(url=url, headers=headers)n with open('./tieba_pages/{}-第{}页.html'.format(kw,i), 'w', encoding='utf-8') as f:n # 为什么是utf-8,因为r.content.decode()为utf-8的格式n f.write(r.content.decode())nu003c/codeu003eu003c/preu003enu003cpu003eu003ccodeu003e扁平胜于嵌套u003c/codeu003e:比如,多用列表推倒式替代某些循环u003c/pu003enu003chru003enu003ch1u003eRequest深入u003c/h1u003enu003ch2u003e发送post请求u003c/h2u003enu003cpu003e用法:u003c/pu003enu003cblockquoteu003enu003cpu003eresponse = u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Frequests.post%28%27https%3A%2F%2Fwww.baidu.com%27%2Cdata%3Ddata%2Cheaders%3Dheaders%29" target="_blank"u003erequests.post('https://www.baidu.com',data=data,headers=headers)u003c/au003eu003cbru003enpost时不仅需要地址,还需要我们post的数据,该数据就放在data中u003cbru003endata的形式:字典u003c/pu003enu003c/blockquoteu003enu003ch2u003e使用代理u003c/h2u003enu003cpu003eu003c/pu003eu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 220px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 29.69%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="741" data-height="220"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-2164be5a387988ec" data-original-width="741" data-original-height="220" data-original-format="image/png" data-original-filesize="38231"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cstrongu003e正向代理与反向代理u003c/strongu003eu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 362px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 47.88%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="756" data-height="362"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-0975975fc56c8c15" data-original-width="756" data-original-height="362" data-original-format="image/png" data-original-filesize="104736"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003en反向代理:浏览器不知道服务器的地址,比如以上的图例中,浏览器知道的只是nginx服务器,因此,及时有攻击,也只能攻击nginx,不能攻击到我们的服务器u003cpu003eu003c/pu003enu003cpu003e正向代理:浏览器知道服务器的地址u003c/pu003enu003cpu003eu003cstrongu003e爬虫为什么要使用代理u003c/strongu003eu003c/pu003enu003culu003enu003cliu003e让服务器以为不是同一个客户端在请求u003c/liu003enu003cliu003e防止我们的真实地址被泄漏,防止被追究u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e使用代理u003c/strongu003eu003c/pu003enu003cblockquoteu003enu003cpu003e用法:requests.get('u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fwww.baidu.com%27%2Cproxies%3Dproxies" target="_blank"u003ehttp://www.baidu.com',proxies=proxiesu003c/au003e)u003cbru003enproxies的形式是字典u003c/pu003enu003cpreu003eu003ccode class="python"u003eproxies={n 'htttp':'http://12.34.56.78:8888', #如果请求的是httpn 'https':'https://12.34.56.78:8888' #如果请求的是https的地址n}nu003c/codeu003eu003c/preu003enu003c/blockquoteu003enu003cpu003e免费代理的网址:u003ca href="https://links.jianshu.com/go?to=https%3A%2F%2Fproxy.mimvp.com%2Ffree.php" target="_blank"u003ehttps://proxy.mimvp.com/free.phpu003c/au003eu003c/pu003enu003cpu003e代理一般可以分为3种:u003c/pu003enu003culu003enu003cliu003e透明代理u003c/liu003enu003cliu003e普匿代理,透明以及普匿,对方是可以追查到我们的真实ip的u003c/liu003enu003cliu003e高匿代理u003c/liu003enu003c/ulu003enu003cpu003e要注意,不是所有的ip都支持发送https的请求,有些也不支持发送post请求u003c/pu003enu003cpu003e代码示例:u003c/pu003enu003cpreu003eu003ccode class="python"u003e"""n0. 准备大量ip地址,组成ip池,随机选择一个ip地址来使用n - 如何随机选择ipn - {'ip':ip,'times':0}n - [{},{},...{}],对这个ip的列表按照使用次数进行排序n 选择使用次数较少的几个ip,从中随机选择一个n1. 检查代理的可用性n - 使用request添加超时参数,判断ip的质量n - 在线代理ip质量检测的网站n"""nimport requestsnnproxies = {"http":'http://123.56.74.13:8080'}nnheaders = {n'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'n}nnr = requests.get('http://www.baidu.com', proxies=proxies,headers=headers)nnprint(r.status_code)nprint(r.request.url)nu003c/codeu003eu003c/preu003enu003ch2u003esession和cookie的使用与处理u003c/h2u003enu003cpu003eu003cstrongu003ecookie和session的区别u003c/strongu003eu003c/pu003enu003culu003enu003cliu003ecookie存储在客户的浏览器上,session存储在服务器上u003c/liu003enu003cliu003ecookie的安全性不如session,别人可以分析存放在本地的cokie并进行cookie欺骗u003c/liu003enu003cliu003esession会在一定时间内保存在服务器上,当访问增多,会比较比较占用服务器的性能u003c/liu003enu003cliu003ecookie保存的数据容量有限(通常是4k),很多浏览器限制一个站点最多保存20个cookieu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e爬虫处理cookie和sessionu003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e带上 cookie和session的好处:u003c/pu003enu003cpu003eu003ccodeu003e能够请求到登录之后的页面u003c/codeu003eu003c/pu003enu003c/liu003enu003cliu003enu003cpu003e带上cookie和session的弊端:u003c/pu003enu003cpu003e一套cookie和session往往和一个用户对应,请求太快、次数太多,容易被服务器识别为爬虫u003c/pu003enu003c/liu003enu003c/ulu003enu003cpu003eu003ccodeu003e不需要cookie的时候尽量不去使用cookieu003c/codeu003eu003c/pu003enu003cpu003e但是为了获取登录之后的页面,我们必须发送带有cookies的请求u003c/pu003enu003cpu003e携带cookie请求:u003c/pu003enu003culu003enu003cliu003e携带一堆cookie进行请求,把cookie组成cookie池u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e如何使用u003c/strongu003eu003c/pu003enu003cpu003erequests提供了一个叫做session的类,来实现客户端和服务器端的会话u003cstrongu003e保持u003c/strongu003eu003c/pu003enu003cblockquoteu003enu003colu003enu003cliu003e实例化一个session对象:session = requests.session()u003c/liu003enu003cliu003e让session发送get或post请求:r = sessioon.get(url=url,data=post_data, headers=headers)u003c/liu003enu003c/olu003enu003c/blockquoteu003enu003cpu003e请求登录之后的网站的思路:u003c/pu003enu003culu003enu003cliu003e实例化sessionu003c/liu003enu003cliu003e先使用session发送请求,登陆对应网站,把cookie保存在session中,u003ccodeu003e这里请求时,url应是表单的action的值,如果没有action项,就尝试抓包,看看当我们提交的时候,究竟给哪个网址发送了post请求;post_data是表单中的要提交的数据,其键为nameu003c/codeu003enu003c/liu003enu003cliu003e再使用session请求登录之后才能访问的网站,session能够自动的携带登录成功时保存在其中的cookie,进行请求u003c/liu003enu003c/ulu003enu003cpu003e案例:访问淘宝的登录后的页面u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport requestsnnsesssion = requests.session()nnheaders = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'}n#注意:在copy User-Agent时,一定要复制全,不能直接在查看的时候copy,容易带上省略号npost_url = 'https://login.m.taobao.com/login.htm'n#post的url一般是在源码中表单的action中找nnpost_data = {n 'TPL_username':'xxxx',n 'TPL_password2':'xxxx'n}#表单中要填写的项nnsesssion.post(url=post_url, data=post_data, headers=headers)nnr = sesssion.get('https://h5.m.taobao.com/mlapp/mytaobao.html',headers=headers)nnwith open('taobao.html', 'w', encoding='utf-8') as f:n f.write(r.content.decode()) #会发现taobao.html中的代码与我们登录淘宝后的https://h5.m.taobao.com/mlapp/mytaobao.html的代码一样,即成功访问了登录淘宝后的页面nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e不发送post请求,使用cookie获取登录后的页面u003c/strongu003eu003c/pu003enu003cpu003e即:直接将cookie加在headers里面,而不必使用session进行postu003c/pu003enu003cpu003e如:u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport requestsnnheaders = {n 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',n 'Cookie':'xxxx'n}nurl = 'https://i.taobao.com/my_taobao.htm'nnr = requests.get(url=url, headers=headers)nnprint(r.content.decode())nu003c/codeu003eu003c/preu003enu003cpu003e也可以对cookies以参数形式传递,cookies为字典u003c/pu003enu003cpreu003eu003ccode class="python"u003er = requests.get('http://xxxx',headers=headers, cookies=cookies)nu003c/codeu003eu003c/preu003enu003culu003enu003cliu003ecookie过期时间很长的u003c/liu003enu003cliu003e在cookie过期之前能够拿到所有的数据,比较麻烦u003c/liu003enu003cliu003e配合其他程序一起使用,其他程序专门获取cookie,当前程序专门请求页面u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003eu003ccodeu003e寻找登录的post地址u003c/codeu003eu003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e在form表单中查找actiond的url地址u003c/pu003enu003culu003enu003cliu003epost的数据是input标签中的name的值作为键,真正的用户名密码作为值的字典,post的url地址就是action对应的url地址u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e抓包,看看当我们提交的时候,究竟给哪个网址发送了post请求u003c/pu003enu003culu003enu003cliu003eu003cpu003e勾选perserve log按钮,防止页面跳转找不到urlu003c/pu003eu003c/liu003enu003cliu003enu003cpu003e寻找post数据,确定参数u003c/pu003enu003culu003enu003cliu003eu003cpu003e参数不会变:(如何确定参数会不会变?多请求几次),直接用,比如密码不是动态加密的时候u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e参数会变u003c/pu003enu003culu003enu003cliu003e参数在当前的响应中u003c/liu003enu003cliu003e通过js生成:定位到对应的js查看u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003eu003ccodeu003e定位想要的jsu003c/codeu003eu003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e法一:对于Chrome浏览器u003c/pu003enu003culu003enu003cliu003e选择登录按钮(或任意绑定了js事件的对象)u003c/liu003enu003cliu003eEventlisteneru003c/liu003enu003cliu003e勾选Framework listenersu003c/liu003enu003cliu003e查看对应的jsu003c/liu003enu003cliu003e找到登录按钮对应的函数u003c/liu003enu003cliu003e(如果遇到某个元素(如:$('.password').value)是干嘛的,可以copy到console中去进行查看;也可以直接对js添加断点)u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e法二:对于Chrome浏览器u003c/pu003enu003culu003enu003cliu003e直接通过Chrome中的search all file的搜索url中的关键字u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e法三u003c/pu003enu003cpu003e添加断点的方式来查看js的操作,通过python进行同样的操作,就可以得到js生成的数据u003c/pu003enu003c/liu003enu003c/ulu003enu003ch2u003eRequests的小技巧u003c/h2u003enu003cpu003eu003cstrongu003ecookie对象与字典的相互转化与url编解码u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e"""1. 把cookie对象(cookiejar)转化为字典"""nimport requestsnr = requests.get('http://www.baidu.com')nprint(r.cookies)nret = requests.utils.dict_from_cookiejar(r.cookies)nprint(ret) #输出:{'BDORZ': '27315'}n"""将字典转化为cookiejar"""nprint(requests.utils.cookiejar_from_dict(ret))nn"""2. url地址的编解码"""nurl = 'http://www.baidu.com'nprint(requests.utils.quote(url)) #输出:http%3A//www.baidu.comnprint(requests.utils.unquote(requests.utils.quote(url))) #输出:http://www.baidu.comnn"""输出结果如下:nu0026lt;RequestsCookieJar[u0026lt;Cookie BDORZ=27315 for .baidu.com/u0026gt;]u0026gt;n{'BDORZ': '27315'}nu0026lt;RequestsCookieJar[u0026lt;Cookie BDORZ=27315 for /u0026gt;]u0026gt;nhttp%3A//www.baidu.comnhttp://www.baidu.comn"""nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e请求SSL证书验证与超时设置u003c/strongu003eu003c/pu003enu003cpu003e如果某网站使用的是https,但是又没有购买ssl证书,等浏览器访问时就会提示不安全,而当我们使用爬虫爬取的就会报错,此时,我们可以用verify=False来解决u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport requestsnr = requests.get('https://www.12306.cn/mormhweb/',verify=False, timeout=10) #如果超时,会报错,因此要结合try使用n"""注意:此时不会报错,但是会warning"""nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e配合状态码判断是否请求成功u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003eassert response.status_code 200 #如果断言失败,会报错,因此应该结合try使用nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e重新请求u003c/strongu003eu003c/pu003enu003cpu003e使用retrying模块,通过装饰器的方式使用u003c/pu003enu003cpreu003eu003ccode class="python"u003e"""重新请求"""nimport requestsnfrom retrying import retrynnheaders= {n 'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'n }n@retry(stop_max_attempt_number=3) #即下面的方法最多可以报错3次,如果3次都报错,则程序报错ndef _parse_url(url):n r = requests.get(url,headers=headers,timeout=0.01)n assert r.status_code 200n return r.content.decode()nndef parse_url(url):n try:n html_str = _parse_url(url)n except:n html_str = Nonen return html_strnif name "main":n url = 'http://www.baidu.com'n print(parse_url(url))nu003c/codeu003eu003c/preu003enu003cpu003eps:安装第三方模块的方法u003c/pu003enu003culu003enu003cliu003epip installu003c/liu003enu003cliu003e下载源码文件,进入解压后的目录:u003ccodeu003epython setup.py installu003c/codeu003enu003c/liu003enu003cliu003enu003ccodeu003exxx.whlu003c/codeu003e文件,安装方法:pip install xxx.whlu003c/liu003enu003c/ulu003enu003chru003enu003ch1u003e数据提取方法u003c/h1u003enu003ch2u003e基础知识u003c/h2u003enu003cpu003eu003cstrongu003e什么是数据提取u003c/strongu003eu003c/pu003enu003cpu003e从响应中获取我们想要的数据u003c/pu003enu003cpu003eu003cstrongu003e数据的分类u003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e非结构话数据:html等u003c/pu003enu003culu003enu003cliu003e处理方法:正则表达式、xpathu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e结构化数据:json、xml等u003c/pu003enu003culu003enu003cliu003e处理方法:转化为python数据类型u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003e主要是看结构清不清晰u003c/pu003enu003ch2u003e数据提取之JSONu003c/h2u003enu003cpu003e由于把json数据转化为python内奸数据类型很简单,所以爬虫缀,我们常使用能够返回json数据的urlu003c/pu003enu003cpu003eJSON(JavaScript Object Notation)是一种轻量级的u003cstrongu003e数据交换格式u003c/strongu003e,它使得人们很容易进行阅读和编写 。同时也方便了机器进行解析和生成,适用于u003cstrongu003e进行数据交换的场景u003c/strongu003e,比如网站前后台间的数据交换u003c/pu003enu003cpu003eQ:哪里能够找到返回json的url呢?u003c/pu003enu003culu003enu003cliu003e使用chrome切换到手机页面u003c/liu003enu003cliu003e抓包手机app的软件u003c/liu003enu003c/ulu003enu003cpu003eu003c/pu003eu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 451px; max-height: 360px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 79.82000000000001%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="451" data-height="360"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-ec9f323966ecf0f3" data-original-width="451" data-original-height="360" data-original-format="image/png" data-original-filesize="35028"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cstrongu003ejson.loads与json.dumpsu003c/strongu003eu003cpu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e"""n1. json.loads 能够把json字符串转换成python类型n2. json.dumps 能够把python类型转换为json字符串,当我们把数据保存问文本的时候常常需要这么做,如果要使其显示中文,可以使用参数:ensure_ascii=False;还使用使用参数:indent=2,使下一级相对上一级有两个空格的缩进n"""nu003c/codeu003eu003c/preu003enu003cpu003e使用json的注意点:u003c/pu003enu003culu003enu003cliu003enu003cpu003ejson中的引号都是双引号;u003c/pu003enu003culu003enu003cliu003enu003cpu003e如果不是双引号u003c/pu003enu003culu003enu003cliu003eeval:能实现简单的字符串和python类型的转化u003c/liu003enu003cliu003ereplace:把单引号替换为双引号u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e往一个文件中写如多个json串,不再是一个json串u003c/pu003enu003culu003enu003cliu003e可以一行写一个json串,按照行来读取u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003ejson.load与json.dumpu003c/strongu003eu003c/pu003enu003cpu003e类文件对象:具有read和write方法的对象就是类文件对象,比如:f = open('a.txt','r'),f就是类文件对象(fp)u003c/pu003enu003cpreu003eu003ccode class="python"u003e"""n1. json.load(类文件对象) #类型为dictn2. json.dump(python类型, 类文件对象) #把python类型放入类文件对象中,也可以使用ensure_ascii和indent参数n"""nu003c/codeu003eu003c/preu003enu003cpu003ejson在数据交换中起到了一个载体的作用,承载着相互传递的数据u003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 279px; max-height: 174px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 62.370000000000005%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="279" data-height="174"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-4db400438bd9041a.png" data-original-width="279" data-original-height="174" data-original-format="image/png" data-original-filesize="10002"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003cpu003e案例:爬取豆瓣u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport requestsnfrom pprint import pprint #pprint:pretty print,实现美化输出nimport jsonnnfrom retrying import retrynnurl = 'https://m.douban.com/rexxar/api/v2/skynet/playlists?from_rexxar=trueu0026amp;for_mobile=1'nnheaders = {n 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36',n # 'Sec-Fetch-Mode': 'cors'n 'Referer': 'https://m.douban.com/movie/beta'n #在本次爬取过程中,必须加上Referer才行n}n@retry(stop_max_attempt_number=3)ndef parse_url(url):n r = requests.get(url,headers=headers, timeout=10)n assert r.status_code 200n return r.content.decode()n nresp_html = parse_url(url)np_resp = json.loads(resp_html)npprint(p_resp)nwith open('douban.json','w', encoding='utf-8') as f:n f.write(json.dumps(p_resp, indent=2, ensure_ascii=False))nu003c/codeu003eu003c/preu003enu003cpu003edouban.json中的部分内容如下:u003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 660px; max-height: 389px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 58.940000000000005%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="660" data-height="389"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-d8dfe39139d47b89" data-original-width="660" data-original-height="389" data-original-format="image/png" data-original-filesize="52164"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cpu003e案例:爬取36kru003c/pu003ennu003cpreu003eu003ccode class="python"u003e"""爬取36kr"""nimport requests,jsonnfrom pprint import pprintnimport renurl = 'https://36kr.com/'nnheaders = {n 'User-Agent':'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36'n}nr = requests.get(url=url, headers=headers,timeout=3)nhtml_str = r.content.decode()nreg = 'u0026lt;span class="item-title weight-bold ellipsis-2"u0026gt;(.*?)u0026lt;/spanu0026gt;' #新闻的标题是直接在html中的nret = re.findall(reg, html_str)npprint(ret)nu003c/codeu003eu003c/preu003enu003cpu003e部分输出结果如下:u003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 184px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 25.840000000000003%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="712" data-height="184"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-72287b68c97a31d0.png" data-original-width="712" data-original-height="184" data-original-format="image/png" data-original-filesize="23127"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003cpu003eu003cstrongu003eu003ccodeu003e爬虫思路总结u003c/codeu003eu003c/strongu003eu003c/pu003enu003culu003enu003cliu003e通常,我们访问某个网站时,得到的是其主页的urlu003c/liu003enu003cliu003e得到了主页的url后,观察我们所需要的数据是否在主页对应的响应中,如果在,直接利用主页的url爬取u003c/liu003enu003cliu003e如果不在主页的url中,查找我们需要的数据,得到其对应url,用该url进行数据的爬取u003c/liu003enu003cliu003e如果相应数据不是在html中,而是json中,用json.loads对数据进行处理u003c/liu003enu003c/ulu003enu003chru003enu003ch1u003e正则表达式复习u003c/h1u003enu003cpu003e所谓正则表达式,即:事先定义好一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤u003c/pu003enu003cpu003eu003cstrongu003e常用的正则表达式的方法有u003c/strongu003eu003c/pu003enu003culu003enu003cliu003ere.compile:编译u003c/liu003enu003cliu003epattern.math:从头找一个u003c/liu003enu003cliu003epattern.search:找一个u003c/liu003enu003cliu003epattern.findall:找所以u003c/liu003enu003cliu003epattern.sub:替换u003c/liu003enu003c/ulu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 616px; max-height: 532px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 86.36%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="616" data-height="532"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-10a87fcb90ac146f" data-original-width="616" data-original-height="532" data-original-format="image/png" data-original-filesize="207424"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cpu003e说明:u003c/pu003ennu003culu003enu003cliu003eu003cpu003eu003ccodeu003e.u003c/codeu003e的Dotall即模式即是在匹配时加上re.Dotall参数,或者re.S,使u003ccodeu003e.u003c/codeu003e能够匹配任意字符u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e记忆:d:digit;s:spaceu003c/pu003eu003c/liu003enu003cliu003eu003cpu003esub的使用,re.sub(reg, new_str, old_str),将匹配到的内容替换为new_stru003c/pu003eu003c/liu003enu003cliu003eu003cpu003ere.findall('a(.*)b', 'str'),能够返回括号中的内容,括号前后的内容起到定位和过滤的效果u003c/pu003eu003c/liu003enu003cliu003eu003cpu003er'a\nb' 可以匹配'a\nb';r'a\nb'而不能匹配'a\nb',r可以忽略转义符号带来的影响,待匹配的字符串里面有 几个\,正则表达式里面也写几个\即可u003c/pu003eu003c/liu003enu003cliu003enu003cpu003eu003cstrongu003ecompile的作用u003c/strongu003eu003c/pu003enu003culu003enu003cliu003e将对应正则表达式能够匹配到的内容放到内存中去,加快匹配的速度u003c/liu003enu003cliu003e使用方法:re.compile(reg)u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003eu003cstrongu003ecompile和sub的结合使用u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003eb = hello1world2np = re.compile('\d')np.findall(b)np.sub('',b) #将b中的所有数字替换为下划线nu003c/codeu003eu003c/preu003enu003cpu003eps:如果是对u003ccodeu003e.u003c/codeu003e进行编译,若想使其能够匹配换行符等,则re.S需要加在编译的使用,而不是匹配的时候u003c/pu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e贪婪模式与非贪婪模式u003c/strongu003eu003c/pu003enu003culu003enu003cliu003e非贪婪模式:u003ccodeu003e.?u003c/codeu003e或者u003ccodeu003e.+?u003c/codeu003enu003c/liu003enu003cliu003e贪婪模式:u003ccodeu003e.u003c/codeu003e或者u003ccodeu003e.+u003c/codeu003enu003c/liu003enu003c/ulu003enu003chru003enu003ch1u003eXPATH和lXMLu003c/h1u003enu003ch2u003e基础知识u003c/h2u003enu003cpu003elxml是一款高性能的python HTML/XML解析器,利用xpath,我们可以快速的u003cstrongu003e定位特定元素u003c/strongu003e以及获取节点信息u003c/pu003enu003cpu003eu003cstrongu003e什么是xpathu003c/strongu003eu003c/pu003enu003cpu003expath(XML Path Language)是一门u003cstrongu003e在HTML/XML文档中查找信息的语言u003c/strongu003e(既然是一种语言,就有自己的语法),克用来在html/xml中对元素和属性进行u003cstrongu003e遍历u003c/strongu003eu003c/pu003enu003cpu003eW3School官方文档:u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fwww.w3school.com.cn%2Fxpath%2Findex.asp" target="_blank"u003ehttp://www.w3school.com.cn/xpath/index.aspu003c/au003eu003c/pu003enu003cpu003eu003cstrongu003exml与html对比u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 306px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 33.52%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="913" data-height="306"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-544f9fc628d2a8c9" data-original-width="913" data-original-height="306" data-original-format="image/png" data-original-filesize="77598"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003cpu003e节点的概念:每个xml标签都称之为节点,比如下图中的u003ccodeu003eu0026lt;booku0026gt;u003c/codeu003e、u003ccodeu003eu0026lt;titleu0026gt;u003c/codeu003e等u003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 569px; max-height: 348px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 61.160000000000004%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="569" data-height="348"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-fd1a0e40923f4a19" data-original-width="569" data-original-height="348" data-original-format="image/png" data-original-filesize="72848"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cpu003ebook节点是title等节点的父节点,title和author等是兄弟节点,此外,还有祖先节点等概念u003c/pu003ennu003cpu003eu003cstrongu003e常用节点选择工具u003c/strongu003eu003c/pu003enu003culu003enu003cliu003eChrome插件XPATH Helperu003c/liu003enu003cliu003e开源的XPATH表达式编辑工具:XMLQuire(XML格式文件可用)u003c/liu003enu003cliu003eFireFox插件 XPath Checkeru003c/liu003enu003c/ulu003enu003ch2u003eXPATH语法u003c/h2u003enu003cpu003eXPATH使用路径表达式来选取xml文档中的节点或者节点集。这些u003cstrongu003e路径表达式u003c/strongu003e和我们在常规的u003cstrongu003e电脑文件系统u003c/strongu003e中看到的表达式非常类似u003c/pu003enu003cpu003eu003ccodeu003e注意:我们写xpath的时候,看的是请求页的响应,而不是elementsu003c/codeu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 271px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 36.67%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="739" data-height="271"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-869c042e96f968cf" data-original-width="739" data-original-height="271" data-original-format="image/png" data-original-filesize="53586"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cpu003e使用Chrome插件的时候,当我们选中标签,该标签会添加属性class='xh-highlight'u003c/pu003ennu003cpreu003eu003ccode class="python"u003e/html 即表示从根节点开始选中html标签n/html/head 选中html标签下的head标签n/html/head/link 选中html标签下的head标签中的所有link标签nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003eu003ccodeu003expath学习重点u003c/codeu003eu003c/strongu003eu003c/pu003enu003culu003enu003cliu003eu003cpu003e使用xpath helper或者是Chrome浏览器中的copy xpath都是从element中提取的数据,但是爬虫获取的是url对应的响应,往往和elements不一样u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e获取属性u003c/pu003enu003culu003enu003cliu003enu003ccodeu003e/html/head/link/@hrefu003c/codeu003e 选择html标签下的head标签下的(所有)link标签中的href属性的值u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e获取文本u003c/pu003enu003culu003enu003cliu003enu003ccodeu003e/html/head/link/text()u003c/codeu003e 即选取标签中的内容,innerHtmlu003c/liu003enu003cliu003enu003ccodeu003e/html//text()u003c/codeu003e 获取html下所有的标签文本u003c/liu003enu003cliu003enu003ccodeu003e//a[text()='下一页']u003c/codeu003e选择文本为下一页三个字的a标签u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e从当前节点往下开始选择与使用@进行元素的定位u003c/pu003enu003culu003enu003cliu003enu003ccodeu003e//liu003c/codeu003e 选中文档中所有的li标签u003c/liu003enu003cliu003enu003ccodeu003e//li//au003c/codeu003e 文档中的所有li中的所有a标签u003c/liu003enu003cliu003enu003ccodeu003e//ul[@id='detail-list']/liu003c/codeu003e 选中文档中的id为'detail-list'的ul标签下的li标签;如果没有id,也可以@class等u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e选择特定节点u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 433px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 53.86%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="804" data-height="433"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-fa0e1361fb171617" data-original-width="804" data-original-height="433" data-original-format="image/png" data-original-filesize="194903"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cpu003e上图中的部分说明:u003c/pu003ennu003culu003enu003cliu003enu003ccodeu003e/bookstore/book[priceu0026gt;35.00]u003c/codeu003ebook用的是子节点中的price标签进行的修饰,此处price的形式为:u003ccodeu003eu0026lt;priceu0026gt;35.00u0026lt;/priceu0026gt;u003c/codeu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e选择未知节点u003c/strongu003eu003c/pu003enu003cpu003eu003c/pu003eu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 406px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 54.21%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="749" data-height="406"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-a4e6de013bec509e" data-original-width="749" data-original-height="406" data-original-format="image/png" data-original-filesize="94667"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enu003cstrongu003e选择若干路径(或的运用)u003c/strongu003eu003cpu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 225px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 28.050000000000004%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="802" data-height="225"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-eb1647a08e669827" data-original-width="802" data-original-height="225" data-original-format="image/png" data-original-filesize="78707"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003ch2u003elXML库u003c/h2u003enu003cpu003eu003cstrongu003e使用入门u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e"""n1. 导入lxml的etree库:from lxml import etree,注意,如果是在pycharm中,可能会报红,但是不影响使用n2. 利用etree.HTML,将字符串转化为Element对象n3. Element对象具有xpath的方法:html = etree.HTML(text) html.xpath('字符串格式的xpath语法')n"""nu003c/codeu003eu003c/preu003enu003cpu003e应用举例:u003c/pu003enu003cpreu003eu003ccode class="python"u003efrom lxml import etreenfrom pprint import pprintnntext = """n u0026lt;tru0026gt; n u0026lt;td class="opr-toplist1-right"u0026gt;586万u0026lt;i class="opr-toplist1-st c-icon c-icon-up"u0026gt;u0026lt;/iu0026gt;u0026lt;/tdu0026gt;n u0026lt;/tru0026gt;n u0026lt;tru0026gt;n u0026lt;td class="opr-toplist1-right"u0026gt;539万u0026lt;i class="opr-toplist1-st c-icon c-icon-up"u0026gt;u0026lt;/iu0026gt;u0026lt;/tdu0026gt;n u0026lt;/tru0026gt;n u0026lt;tru0026gt;n u0026lt;td class="opr-toplist1-right"u0026gt;444万u0026lt;i class="opr-toplist1-st c-icon c-icon-up"u0026gt;u0026lt;/iu0026gt;u0026lt;/tdu0026gt;n u0026lt;/tru0026gt;n u0026lt;tru0026gt;n u0026lt;td class="opr-toplist1-right"u0026gt;395万u0026lt;i class="opr-toplist1-st c-icon "u0026gt;u0026lt;/iu0026gt;u0026lt;/tdu0026gt;n"""nhtml = etree.HTML(text)n#html为一个Element对象npprint(html)n#查看element对象中包含的字符串(bytes类型)npprint(etree.tostring(html).decode()) #会发现把缺少的标签进行了补全,包括html和body标签nprint(html.xpath('//td/text()')) #这里的html是上面etree.HTML(text)获得的对象,结果为列表n#只要是element对象,就可以使用xpath进行数据的提取nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003elxml注意点u003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003elxml可以自动修正html代码(但是不一定能正确修正,也可能改错了)u003c/pu003enu003culu003enu003cliu003e使用etree.tostring查看修改后的样子,根据修改之后的html字符串写xpathu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e提取页面数据的思路u003c/pu003enu003culu003enu003cliu003e先分组,渠道:一个包含分组标签的列表u003c/liu003enu003cliu003e遍历:取其中每一组进行数据的提取,不会造成数据对应错乱u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003expath的包含u003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003ccodeu003e//div[contains(@class='li')]u003c/codeu003e获取包含有li样式类的标签的标签u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e爬虫的思路总结u003c/strongu003eu003c/pu003enu003colu003enu003cliu003enu003cpu003e准备urlu003c/pu003enu003colu003enu003cliu003enu003cpu003e准备start_urlu003c/pu003enu003culu003enu003cliu003eu003cpu003eurl地址规律不明显,总数不确定u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e通过代码提取下一页urlu003c/pu003enu003culu003enu003cliu003expath:url在当前的响应里面u003c/liu003enu003cliu003e寻找url地址,部分参数在当前的响应中,比如当前页面数和总的页码数(eg:通过js生成)u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e准备url_listu003c/pu003enu003culu003enu003cliu003e页码总数明确u003c/liu003enu003cliu003eurl地址规律明显u003c/liu003enu003c/ulu003enu003c/liu003enu003c/olu003enu003c/liu003enu003cliu003enu003cpu003e发送请求,获取响应u003c/pu003enu003culu003enu003cliu003eu003cpu003e添加随机的User-Agent:反反爬虫u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e添加随机的代理ip:反反爬虫u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e在对方判断出我们是爬虫之后,添加更多的header字段,包括cookieu003c/pu003enu003culu003enu003cliu003eu003cpu003ecookie的处理可以通过session来解决u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e准备一堆能用的cookie,组成cookie池u003c/pu003enu003culu003enu003cliu003enu003cpu003e如果不登录,准备当开始能够请求对方网站的cookie,即接受对方网站设置在response的cookieu003c/pu003enu003culu003enu003cliu003e下一次请求的时候,使用之前的列表中的cookie来请求u003c/liu003enu003cliu003e即:专门用一个小程序来获取cookie,爬取数据再用另一个程序u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e如果登录u003c/pu003enu003culu003enu003cliu003e准备一堆账号u003c/liu003enu003cliu003e使用程序获取每个账号的cookieu003c/liu003enu003cliu003e之后请求登录之后才能访问的网站随机的选择cookieu003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e提取数据u003c/pu003enu003culu003enu003cliu003enu003cpu003e确定数据的位置u003c/pu003enu003culu003enu003cliu003enu003cpu003e如果数据在当前的url地址中u003c/pu003enu003culu003enu003cliu003enu003cpu003e提取的是列表页的数据u003c/pu003enu003culu003enu003cliu003e直接请求列表页的url地址,不用进入详情页u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e提取的是详情页的数据u003c/pu003enu003culu003enu003cliu003e确定url地址u003c/liu003enu003cliu003e发送请求u003c/liu003enu003cliu003e提取数据u003c/liu003enu003cliu003e返回u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e如果数据不在当前的url地址中u003c/pu003enu003culu003enu003cliu003e在其他的响应中,寻找数据的位置u003c/liu003enu003cliu003e使用chrome的过滤条件,选择除了js,css,img之外的按钮(但是可能出错)u003c/liu003enu003cliu003e使用chrome的search all file,搜索数字和英文(有时候不支持搜索中文)u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e数据的提取u003c/pu003enu003culu003enu003cliu003expath,从html中提取整块数据,先分组,之后没一组再提取u003c/liu003enu003cliu003ejsonu003c/liu003enu003cliu003ere,提取max_time,price,html中的json字符串u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e保存u003c/pu003enu003culu003enu003cliu003e保存在本地,text、json、csvu003c/liu003enu003cliu003e保存在数据库u003c/liu003enu003c/ulu003enu003c/liu003enu003c/olu003enu003ch2u003eCSVu003c/h2u003enu003cpu003e逗号分隔值,一种文件后缀,以纯文本的形式存储表格数据u003c/pu003enu003cpu003e其文件中的一行对应表格的一行,以逗号分隔列u003c/pu003enu003chru003enu003ch1u003e多线程爬虫u003c/h1u003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 231px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 30.8%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="750" data-height="231"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-3d9b88d03baa6174" data-original-width="750" data-original-height="231" data-original-format="image/png" data-original-filesize="62666"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003chru003enu003ch1u003e动态HTML技术u003c/h1u003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 279px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 34.4%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="811" data-height="279"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-bfdbd81670e702fb" data-original-width="811" data-original-height="279" data-original-format="image/png" data-original-filesize="98141"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003ch2u003eSelenium和PhantomJSu003c/h2u003enu003culu003enu003cliu003enu003cpu003eSeleniumu003c/pu003enu003cpu003eSelenium是一个Web的自动化测试工具,可以控制一些浏览器(比如phantomJS),可以接受指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏u003c/pu003enu003c/liu003enu003cliu003enu003cpu003ePhantomJSu003c/pu003enu003cpu003ephantomJS是一个基于Webkit的“无界面”浏览器,它会把网站加载到内存并执行页面上的javascriptu003c/pu003enu003c/liu003enu003c/ulu003enu003cpu003e下载地址:u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fphantomjs.org%2Fdownload.html" target="_blank"u003ehttp://phantomjs.org/download.htmlu003c/au003eu003c/pu003enu003ch3u003e入门u003c/h3u003enu003cpreu003eu003ccode class="python"u003e"""1. 加载网页"""nfrom selenium import webdriverndriver = webdriver.PhantomJS("xxxx/phantom.exe")n"""除了PhantomJS,还有Chrome,FireFox等"""ndriver.get("http://www.baiud.com/")ndriver.save_screenshot("长城.pnh")nn"""2. 定位和操作"""ndriver.find_element_byid("kw").send_keys("长城")ndrvier.finde_element_by_id("su").click()nn"""3. 查看和请求信息"""ndriver.page_source()ndriver.get_cookies()ndriver.current_url()nn"""4. 退出"""ndriver.close() #退出当前页面ndriver.quit() #退出浏览器nn"""5. 其他"""n#ps:无论是使用PhantomJS还是Chrome或是FireFox,driver的操作是一样的nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e基础使用示例u003c/strongu003eu003c/pu003enu003cpu003eps:chromedriver的下载地址(注意:版本一定要和你安装的Chrome浏览器的版本号一致):u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fnpm.taobao.org%2Fmirrors%2Fchromedriver%2F" target="_blank"u003ehttp://npm.taobao.org/mirrors/chromedriver/u003c/au003eu003c/pu003enu003cpreu003eu003ccode class="python"u003efrom selenium import webdrivern"""nselenium请求的速度很慢,因为是使用浏览器,会请求js、css等n"""nnphantom_path = r"D:\Green\phantomjs-2.1.1-windows\bin\phantomjs.exe"n"""在使用phatnomjs时,报了unexpected exit, status code 0的错误,尚未找到原因"""nchrome_path = r"C:\Users\25371\Desktop\chromedriver_win32\chromedriver.exe"n"""注意:这里一定要是chromedriver.exe,而不是chrome.exe"""nndriver = webdriver.Chrome(executable_path=chrome_path) #实例化对象n# driver.maximize_window() #最大化窗口ndriver.set_window_size(width=1920,height=1080) #设置窗口大小ndriver.get("http://www.baidu.com")ndriver.find_element_by_id('kw').send_keys("python")n#kw是百度的输入框的表单的id;send_keys就是往一个input标签里面输入内容n#以上的一行代码就可以时Chrome自己百度搜索pythonndriver.find_element_by_id('su').click()n#su是百度一下的按钮的idn#click实现对按钮的点击nn"""获取当前的url"""nprint(driver.current_url) #注意:因为已经click了,所以是click后的地址nn"""截屏"""ndriver.save_screenshot("./baidu_python.png")n"""在本次截屏中,由于截屏太快而网页加载太慢,截屏的图中未能截到百度出来的结果"""nn"""driver获取cookie"""ncookies = driver.get_cookies()nprint(cookies)ncookies = {i['name']:i['value'] for i in cookies} #使用字典推导式重新生成requests模块能用的cookiesnprint(cookies)nn"""获取html字符串"""n"""即elements"""nprint(driver.page_source) #page_source是一个属性,获得html字符串后,就可以直接交给xpathnn"""退出当前页面"""ndriver.close() #如果只有一个窗口,close就是退出浏览器n"""退出浏览器"""ndriver.quit()nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e示例二u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003efrom selenium import webdrivernfrom time import sleepnchrome_path = r"C:\Users\25371\Desktop\chromedriver_win32\chromedriver.exe"ndriver = webdriver.Chrome(executable_path=chrome_path)ndriver.get('https://www.qiushibaike.com/text/page/1/')nret = driver.find_elements_by_xpath(".//div[@id='content-left']/div")nfor r in ret:n print(r.find_element_by_xpath("./a[1]/div[@class='content']/span").text)n """通过text属性获取文本"""n print(r.find_element_by_xpath("./a[1]").get_attribute("href"))n """通过get_attribute获取属性"""nndriver.quit()nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e元素的定位方法u003c/strongu003eu003c/pu003enu003culu003enu003cliu003efind_element_by_id 返回一个u003c/liu003enu003cliu003efind_elements_by_id 返回一个列表u003c/liu003enu003cliu003efind_elements_by_link_textu003c/liu003enu003cliu003efind_elements_by_partial_link_textu003c/liu003enu003cliu003efind_elements_by_tag_nameu003c/liu003enu003cliu003efind_element_by_class_nameu003c/liu003enu003cliu003efind_elements_by_class_nameu003c/liu003enu003cliu003efind_elements_by_css_selectoru003c/liu003enu003c/ulu003enu003cpu003e注意:u003c/pu003enu003culu003enu003cliu003e获取文本或属性时,需要先定位到对应元素,再使用text属性或者get_attribute方法u003c/liu003enu003cliu003eelement返回一个,elements返回列表u003c/liu003enu003cliu003elink_text和partial_link_text的区别:全部文本和包含的某个文本,即partial可以只写部分文本,而link_text需要写完整u003c/liu003enu003cliu003eby_css_selector的用法:#food span.dairy.agedu003c/liu003enu003cliu003eby_xpath中获取属性和文本需要使用get_attribute()和.textu003c/liu003enu003cliu003eselenium使用class定位标签的时候,只需要其中的一个class样式名即可,而xpath必须要写所有的class样式类名u003c/liu003enu003c/ulu003enu003cpu003e示例:u003c/pu003enu003cpreu003eu003ccode class="python"u003efrom selenium import webdrivernfrom time import sleepnchrome_path = r"C:\Users\25371\Desktop\chromedriver_win32\chromedriver.exe"nndriver = webdriver.Chrome(executable_path=chrome_path)nndriver.get('https://www.qiushibaike.com/text/page/1/')nnret = driver.find_elements_by_xpath(".//div[@id='content-left']/div")nfor r in ret:n print(r.find_element_by_xpath("./a[1]/div[@class='content']/span").text)n """通过text属性获取文本"""n print(r.find_element_by_xpath("./a[1]").get_attribute("href"))n """通过get_attribut获取属性"""nnprint('-'*50)n"""find_element_by_link_text"""n"""根据标签里面的文字获取元素"""nprint(driver.find_element_by_link_text("下一页").get_attribute('href'))n"""partial_link_text"""nprint(driver.find_element_by_partial_link_text("下一").get_attribute('href'))n"""以上两行代码获得的东西相同"""ndriver.quit()nu003c/codeu003eu003c/preu003enu003ch3u003e深入u003c/h3u003enu003cpu003eu003cstrongu003eiframeu003c/strongu003eu003c/pu003enu003cpu003eiframe或frame里面的html代码和外面的html代码实质上是两个不同的页面,因此,有时候我们在定位元素时,明明elements里面有,但是会定位失败u003c/pu003enu003cpu003e解决办法:使用driver.switch_to.frame或driver.switch_to_frame(已经被弃用)方法切换到对应frame中去u003c/pu003enu003cpu003edriver.switch_to.frame的使用说明:u003c/pu003enu003cpreu003eu003ccode class="python"u003edef frame(self, frame_reference):n"""n Switches focus to the specified frame, by index, name, or webelement.nn :Args:n - frame_reference: The name of the window to switch to, an integer representing the index,n or a webelement that is an (i)frame to switch to.nn :Usage:n driver.switch_to.frame('frame_name')n driver.switch_to.frame(1)n driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0])n """n 源码nu003c/codeu003eu003c/preu003enu003cpu003e代码示例:豆瓣登录u003c/pu003enu003cpreu003eu003ccode class="python"u003efrom selenium import webdrivernimport timenchrome_path = r"C:\Users\25371\Desktop\chromedriver_win32\chromedriver.exe"ndriver = webdriver.Chrome(executable_path=chrome_path)nndriver.get("https://douban.com/")nlogin_frame = driver.find_element_by_xpath("//iframe[1]")ndriver.switch_to.frame(login_frame)ndriver.find_element_by_class_name("account-tab-account").click()ndriver.find_element_by_id("username").send_keys("784542623@qq.com")ndriver.find_element_by_id("password").send_keys("zhoudawei123")nndriver.find_element_by_class_name("btn-account").click()ntime.sleep(10) #暂停以手动进行验证nn"""获取cookies"""ncookies = {i['name']:i['value'] for i in driver.get_cookies()}nprint(cookies)nntime.sleep(3)ndriver.quit()nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e注意u003c/strongu003eu003c/pu003enu003culu003enu003cliu003eselenium获取的页面数据 是浏览器中elements的内容u003c/liu003enu003cliu003eselenium请求第一页的时候,会等待页面加载完了之后再获取数据,但是在点击翻页之后,会立马获取数据,此时可能由于页面还没有加载完而报错u003c/liu003enu003c/ulu003enu003ch3u003e其他u003c/h3u003enu003culu003enu003cliu003enu003cpu003ecookies相关用法u003c/pu003enu003culu003enu003cliu003e{cookie['name']:cookie['value'] for cookie in driverr.get_cookies()} 获取字典形式的cookieu003c/liu003enu003cliu003edriver.delete_cookie('cookiename')u003c/liu003enu003cliu003edriver.delete_all_cookies()u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e页面等待u003c/pu003enu003cpu003e页面等待的原因:如果网站采用了动态html技术,那么页面上的部分元素出现时间便不能确定,这个时候就需要一个等待时间u003c/pu003enu003culu003enu003cliu003e强制等待:time.sleep(10)u003c/liu003enu003cliu003e显示等待(了解)u003c/liu003enu003cliu003e隐式等待(了解)u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003chru003enu003ch1u003eTesseract的使用u003c/h1u003enu003colu003enu003cliu003eu003cpu003etesseract是一个u003cstrongu003e将图像翻译成文字的OCR库u003c/strongu003e,ocr:optical character recognitionu003c/pu003eu003c/liu003enu003cliu003enu003cpu003e在python中安装tesseract模块:pip install pytesseract,使用方法如下:u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport pytesseractnfrom PIL import Imagenimage = Image.open(jpg) #jpg为图片的地址npytesseract.image_to_string(image)nu003c/codeu003eu003c/preu003enu003c/liu003enu003c/olu003enu003cpu003e使用tesseract和PIL,我们就可以使程序能够识别验证码(当然,也可以通过打码平台进行验证码的识别)u003c/pu003enu003cpu003e示例:对如下图片进行识别u003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 413px; max-height: 342px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 82.80999999999999%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="413" data-height="342"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-7e453405c3d53aa0" data-original-width="413" data-original-height="342" data-original-format="image/png" data-original-filesize="107982"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003cpreu003eu003ccode class="python"u003eimport pytesseractnfrom PIL import Imagenimg_url = "verify_code.jpg"npytesseract.pytesseract.tesseract_cmd = r'D:\Tesseract-OCR\tesseract.exe'nimage = Image.open(img_url)nnprint(pytesseract.image_to_string(image))nn"""n使用过程中的问题:n 1. 电脑上必须先安装tesseract客户端,然后才能结合pytesseract使用n 2. 将tesseract加入环境变量n 3. 在环境变量中新建项:名字:TESSDATA_PREFIX,值:你的tesseract的安装目录(tessdata的父级目录)n 4. 在代码中加入:pytesseract.pytesseract.tesseract_cmd = r"tesseract的安装路径\tesseract.exe"n 5. 默认只识别英语,如果要识别其他语言,需要下载相关语言的.traneddata文件到Tesseract的安装目录下的tessdata路径下:https://github.com/tesseract-ocr/tesseract/wiki/Data-Filesn n"""n"""识别结果如下:nHappynBirthdayn"""nu003c/codeu003eu003c/preu003enu003chru003enu003ch1u003eMongodbu003c/h1u003enu003cpu003e注意:以下用到的集合大多为stu(学生信息),少部分为productsu003c/pu003enu003ch2u003e基础入门u003c/h2u003enu003cpu003eMongodb是一种NoSQL数据库u003c/pu003enu003cpu003emysql的扩展性差,大数据下IO压力大,表结构更改困难;而nosql易扩展,大数据量高性能,灵活的数据模型,高可用u003c/pu003enu003cpu003e下载地址:u003ca href="https://links.jianshu.com/go?to=https%3A%2F%2Fwww.mongodb.com%2Fdownload-center%2Fcommunity" target="_blank"u003ehttps://www.mongodb.com/download-center/communityu003c/au003eu003c/pu003enu003cpu003eu003cstrongu003emongodb的使用u003c/strongu003eu003c/pu003enu003culu003enu003cliu003enu003cpu003e在终端运行u003ccodeu003eMongoDB\bin\mongod.exe --dbpath D:\MongoDB\datau003c/codeu003e,其中,D:\MongoDB是安装路径,(注意:下图中我在安装时把MongoDB写成了MonggoDBu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 348px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 36.29%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="959" data-height="348"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-e563d7741894c906" data-original-width="959" data-original-height="348" data-original-format="image/png" data-original-filesize="52669"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003culu003enu003cliu003e因为在安装时我们默认安装了MongoDB Compass Community,我们打开该软件,直接连接即可,不用对其做任何更改,成功后如图所示:u003c/liu003enu003c/ulu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 381px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 52.190000000000005%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="730" data-height="381"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-9aaf73253829ec76" data-original-width="730" data-original-height="381" data-original-format="image/png" data-original-filesize="43807"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003culu003enu003cliu003e如果我们要使用数据库,还需要将安装目录下小bin目录加入系统的环境变量,然后在终端输入mongo即可u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003eps:在mongo的交互环境中,可以通过tab键进行命令的补全u003c/pu003enu003cpu003eu003cstrongu003edatabase的基础命令u003c/strongu003eu003c/pu003enu003culu003enu003cliu003e查看当前数据库:dbu003c/liu003enu003cliu003e查看所有的数据库:show dbs或show databasesu003c/liu003enu003cliu003e切换数据库:use db_nameu003c/liu003enu003cliu003e删除当前数据库:db.dropDatabase()u003c/liu003enu003c/ulu003enu003cpu003e在mongodb里面,是没有表的概念的,数据是存储在集合中u003c/pu003enu003cpu003e向不存在的集合中第一次插入数据时,集合会被创建出来u003c/pu003enu003cpu003e手动创建集合:u003c/pu003enu003culu003enu003cliu003edb.createCollection(name,options)u003c/liu003enu003cliu003eoptions是一个字典,例如:{size:10, capped:true} #表示存储的上限为10条数据,capped为true表示当数据达到上限时,会覆盖之前的数据u003c/liu003enu003c/ulu003enu003culu003enu003cliu003e查看集合:show collectionsu003c/liu003enu003cliu003e删除集合:db.collection_name.drop()u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003emongodb中的数据类型u003c/strongu003eu003c/pu003enu003culu003enu003cliu003eObjectID:文档id,所谓文档,即我们即将存储到数据库中的一个个的字典u003c/liu003enu003cliu003eStringu003c/liu003enu003cliu003eBoolean:必须是小写,true或falseu003c/liu003enu003cliu003eIntegeru003c/liu003enu003cliu003eDoubleu003c/liu003enu003cliu003eArraysu003c/liu003enu003cliu003eObject:用于嵌入式的文档,即一个值为一个文档u003c/liu003enu003cliu003eNullu003c/liu003enu003cliu003eTimestamp:时间戳u003c/liu003enu003cliu003eDate:创建日期的格式:new Date("2019-02-01")u003c/liu003enu003c/ulu003enu003cpu003e注意点:u003c/pu003enu003culu003enu003cliu003e每个文档的都有一个属性,为_id,保证文档的唯一性u003c/liu003enu003cliu003e可以自己设置_id插入文档,如果没有提供,自动生成,类型为Object_idu003c/liu003enu003cliu003eobjecID是一个12字节的16进制数,4:时间戳,3:机器id,2:mongodb的服务进程id,3:简单的增量值u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e数据的操作u003c/strongu003eu003c/pu003enu003culu003enu003cliu003e用insert插入:先use数据库,u003ccodeu003edb.集合名.insert({"name":"zhang3", "age":23})u003c/codeu003e,实质上插入的数据不是字典,而是json,因此键可以不用引号。insert的时候如果文档id已经存在,会报错u003c/liu003enu003cliu003e查看表中的数据:u003ccodeu003edb.表名.find()u003c/codeu003enu003c/liu003enu003cliu003e用save进行数据的插入:u003ccodeu003edb.集合名.save(要插入的数据)u003c/codeu003e,如果文档id(对应我们要插入的数据)已经存在,就是修改,否则新增u003c/liu003enu003cliu003e查看集合中的数据:u003ccodeu003edb.集合名称.find()u003c/codeu003enu003c/liu003enu003cliu003e更新:db.集合名称.u003ccodeu003eupdate(u0026lt;queryu0026gt;,u0026lt;updateu0026gt;,{multi:u0026lt;booleanu0026gt;})u003c/codeu003e,用法如下:nu003cpreu003eu003ccode class="python"u003edb.stu.upate({name:'hr'}, {name:'mnc'})#更新一条的全部ndb.stu.update({name:'hr'}, {set:{name:'mnc'}}) #更新一条中的对应键值n"""这种更改用得更多"""ndb.stu.update({},{set:{gender:0}}, {multi:true}) #更新全部n"""注意:multi这个参数必须和符号一起使用才有效果"""nu003c/codeu003eu003c/preu003enu003c/liu003enu003cliu003e使用remove删除数据:db.集合名.remove({name:"zhang3"},{justOne:true}),表示只删除一条名字为zhang3的数据,如果不指定justOne,就是删除全部符合的数据u003c/liu003enu003c/ulu003enu003ch2u003e高级查询u003c/h2u003enu003cpu003eu003cstrongu003efindu003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e"""find"""ndb.stu.find() #查询所有的数据ndb.stu.find({age:23}) #查询满足条件的数据ndb.stu.findOne({age:23}) #查询满足条件的一个数据ndb.stu.find().pretty() #对数据进行美化nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e比较运算符u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e1. 等于:默认是等于判断,没有运算符n2. 小于:lt(less than)n3. 大于:gt(greater than)n4. 小于等于:lte(less than equal)n5. 大于等于:gte(greater than equal)n6. 不等于:ne(not equal)nu003c/codeu003eu003c/preu003enu003cpu003e使用举例:u003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.find({age:{le(18)}}) #查询年龄小于18的nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e范围运算符u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e1.in:在某个范围n2. nin:不在某个范围nu003c/codeu003eu003c/preu003enu003cpu003e用法举例:u003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.find({age:{in[18,28,38]}}) #查询年龄为18或28或38的nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e逻辑运算符u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003eand:直接写多个条件即可,例:db.stu.find({age:{gte:18},gender:true})nor:使用or,值为数组,数组中的每个元素为json,例:查询年龄大于18或性别为false的数据:db.stu.find({or:[{age:{gt:18}},{gender:{false}}]})nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e正则表达式u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e1. db.products.find({sku:/^abc/}) #查询以abc开头的skun2. db.products.find({sku:{regex:"789"}}) #查询以789结尾的skunu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003elimit和skipu003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003e1. db.stu.find().limit(2) #查询两个学生的信息n2. db.stu.find().skip(2) #跳过前两个学生的信息n3. db.stu.find().skip(2).limit(4) #先跳过2个,再查找4个nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e自定义查询u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.find({where:function(){n return this.age u0026gt; 30;n}}) #查询年龄大于30的学生nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e投影u003c/strongu003eu003c/pu003enu003cpu003e即返回满足条件的数据中的部分内容,而不是返回整条数据u003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.find({where:function(){n return this.age u0026gt; 30, {name:1,hometown:1};n}}) #查询年龄大于30的学生,并返回其名字和hometown,其中,this是指从前到后的每一条数据;如果省略{name:xxx}就会返回该条数据的全部内容nndb.stu.find({},{_id:0,name:1,hometown:1}) #显示所有数据的name和hometown,不显示_id,但是要注意,只有_id可以使用0;一般对其他字段来说,要显示的写1,不显示的不写即可,_id默认是会显示的nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e排序u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.find().sort({age:-1}) #按年龄的降序排列,如果是{age:1}就是按按铃升序排序ndb.stu.find().sort({age:1,gender:-1}) #按年龄的升序排列,如果年龄相同,按gender的降序排列nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003ecount方法u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.find({条件}).count() #查看满足条件的数据有多少条ndb.stu.count({条件})nu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003e消除重复u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003edb.stu.distinct("去重字段",{条件})ndb.stu.distinct("hometown",{age:{gt:18}}) #查看年龄大于18的人都来自哪几个地方nu003c/codeu003eu003c/preu003enu003ch2u003e聚合aggregateu003c/h2u003enu003cpu003e聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。u003cbru003endb.集合名称.aggregate({管道:{表达式}})u003c/pu003enu003cpu003e所谓管道,即把上一次的输出结果作为下一次的输入数据u003c/pu003enu003cpu003eu003cstrongu003e常用管道如下u003c/strongu003e:u003c/pu003enu003culu003enu003cliu003egroup: 将集合中的⽂档分组, 可⽤于统计结果u003c/liu003enu003cliu003ematch: 过滤数据, 只输出符合条件的⽂档,match:匹配u003c/liu003enu003cliu003eproject: 修改输⼊⽂档的结构, 如重命名、 增加、 删除字段、 创建计算结果u003c/liu003enu003cliu003esort: 将输⼊⽂档排序后输出u003c/liu003enu003cliu003elimit: 限制聚合管道返回的⽂档数u003c/liu003enu003cliu003eskip: 跳过指定数量的⽂档, 并返回余下的⽂档u003c/liu003enu003cliu003eunwind: 将数组类型的字段进⾏拆分,即展开的意思u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e表达式u003c/strongu003eu003c/pu003enu003cpu003e语法:表达式:'列名'u003cbru003en常⽤表达式:u003c/pu003enu003culu003enu003cliu003enu003cimg class="math-inline" src="https://math.jianshu.com/math?formula=sum%EF%BC%9A%20%E8%AE%A1%E7%AE%97%E6%80%BB%E5%92%8C%EF%BC%8C" alt="sum: 计算总和," mathimg="1"u003esum:1 表示以⼀倍计数u003c/liu003enu003cliu003eavg: 计算平均值u003c/liu003enu003cliu003emin: 获取最⼩值u003c/liu003enu003cliu003emax: 获取最⼤值u003c/liu003enu003cliu003epush: 在结果⽂档中插⼊值到⼀个数组中u003c/liu003enu003cliu003efirst: 根据资源⽂档的排序获取第⼀个⽂档数据u003c/liu003enu003cliu003elast: 根据资源⽂档的排序获取最后⼀个⽂档数据u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e用法示例u003c/strongu003e:u003c/pu003enu003cpreu003eu003ccode class="python"u003e"""group的使用"""ndb.Temp.aggregate(n {group:{_id:"gender"}}n) #按性别分组n"""输出结果:n{ "_id" : 1 }n{ "_id" : 0 }n"""ndb.Temp.aggregate( n{group:{_id:"gender",count:{sum:1}}} n) #按性别分组并计数,sum:1是指每条数据作为1n"""输出结果如下:n{ "_id" : 1, "count" : 7 }n{ "_id" : 0, "count" : 1 }n"""n"""注意:_id和count的键不能变"""ndb.Temp.aggregate( n {group:{_id:"gender",n count:{sum:1},n avg_age:{avg:"age"}}} n) #按年龄分组并计数,再分别计算其年龄的平均值n"""结果如下:n{ "_id" : 1, "count" : 7, "avg_age" : 22.857142857142858 }n{ "_id" : 0, "count" : 1, "avg_age" : 32 }n"""n"""注意:如果分组时_id:null,则会将整个文档作为一个分组"""nnnn"""管道的使用"""ndb.Temp.aggregate(n {group:{_id:"gender",count:{sum:1},avg_age:{avg:"age"}}},n {project:{gender:"_id",count:"count",avg_age:"avg_age"}}n) #将group的输出再作为project的输入,因为前面已经有了_id,count,avg_age等输出键,所以在后面的管道中可以直接使用(此例中用了_id和avg_age),也可以使用1使其显示,0使其不显示n"""输出结果如下n{ "count" : 7, "gender" : 1, "avg_age" : 22.857142857142858 }n{ "count" : 1, "gender" : 0, "avg_age" : 32 }n"""nnn"""match管道的使用"""n#为什么使用match过滤而不是find的过滤?match可以将其数据交给下一个管道处理,而find不行ndb.Temp.aggregate(n {match:{age:{gt:20}}},n {group:{_id:"gender",count:{sum:1}}},n {project:{_id:0,gender:"_id",count:1}}n) #先选择年龄大于20的数据;然后将其交给group管道处理,按照性别分组,对每组数据进行计数;然后再将其数据交给project处理,让_id字段显示为性别,不显示_id字段,显示count字段nn"""sort管道的使用"""ndb.Temp.aggregate(n {group:{_id:"gender",count:{sum:1}}},n {sort:{count:-1}}n) #将第一个管道的数据按照其count字段的逆序排列,和find中的排序使用方式一样n"""结果如下:n{ "_id" : 1, "count" : 7 }n{ "_id" : 0, "count" : 1 }n"""nn"""skip和limit的用法示例:n{limit:2}n{skip:5}n"""nn"""unwind使用使例:"""neg:假设某条数据的size字段为:['S','M','L'],要将其拆分ndb.Temp.aggregate(n {match:{size:["S","M","L"]}}, #先找到该数据n {unwind:"size"}n)n"""结果如下:n{ "_id" : ObjectId("5d57cada783675188ccd7ee0"), "size" : "S" }n{ "_id" : ObjectId("5d57cada783675188ccd7ee0"), "size" : "M" }n{ "_id" : ObjectId("5d57cada783675188ccd7ee0"), "size" : "L" }n"""nnnu003c/codeu003eu003c/preu003enu003cpu003e小练习:u003c/pu003enu003cpreu003eu003ccode class="python"u003e"""数据和需求:n{ "country" : "china", "province" : "sh", "userid" : "a" } n{ "country" : "china", "province" : "sh", "userid" : "b" } n{ "country" : "china", "province" : "sh", "userid" : "a" } n{ "country" : "china", "province" : "sh", "userid" : "c" } n{ "country" : "china", "province" : "bj", "userid" : "da" } n{ "country" : "china", "province" : "bj", "userid" : "fa" } n需求:统计出每个country/province下的userid的数量(同一个userid只统计一次)n"""ndb.Exci.aggregate(n {group:{_id:{userid:"userid",province:"province",country:"country"}}}, #先按照三个字段分组(去重)n {group:{_id:{country:"_id.country",province:"_id.province"},count:{sum:1}}},n {project:{_id:0,country:"_id.country",province:"_id.province",count:"count"}}n)n"""注意:取字典里面的元素用(.)操作符;group的_id可以为字典"""n"""三个管道处理过后的数据分别如下:n#第一次groupn{ "_id" : { "userid" : "a", "province" : "sh", "country" : "china" } }n{ "_id" : { "userid" : "b", "province" : "sh", "country" : "china" } }n{ "_id" : { "userid" : "c", "province" : "sh", "country" : "china" } }n{ "_id" : { "userid" : "da", "province" : "bj", "country" : "china" } }n{ "_id" : { "userid" : "fa", "province" : "bj", "country" : "china" } }nn#第二次groupn{ "_id" : { "country" : "china", "province" : "bj" }, "count" : 2 }n{ "_id" : { "country" : "china", "province" : "sh" }, "count" : 3 }nn#最终结果n{ "country" : "china", "province" : "sh", "count" : 3 }n{ "country" : "china", "province" : "bj", "count" : 2 }n"""nn"""也可以写成如下形式:"""ndb.Temp.aggregate(n {match:{size:["S","M","L"]}},n {unwind:{path:"$size",preserveNullAndEmptyArrays:true}}n) #path字段是要拆分的字段,参数表示保存Null和EmptyArrays,因为如果是原数据中有某字段为null或[],那么在拆分一个数据后,表中原来含nul或[]的那几条数据会消失nu003c/codeu003eu003c/preu003enu003ch2u003e索引u003c/h2u003enu003cpu003e作用:提升查询速度u003c/pu003enu003cpu003edb.t1.find({查询条件})u003cbru003endb.t1.find({查询条件}).explain('executionStats') 可以通过其中的"executionTimeMillisEstimate"字段查看查询所花费的时间u003c/pu003enu003cpu003eu003cstrongu003e建立索引u003c/strongu003eu003c/pu003enu003culu003enu003cliu003eu003cpu003e语法:db.集合.ensureIndex({属性1:1}, {unique:true}) 其中,1表示升序,-1表示降序,一般来说,升序或降序的影响不大;unique字段可以省略,加上后,保证索引唯一,即:如果我们用name作为索引,那么在集合中就不能有name值相同的数据;u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e联合索引,即ensureIndex的参数为{属性1:1或-1, 属性2:-1或1},联合索引是为了保证数据的唯一性u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e唯一索引的作用:比如,当我们爬取数据时,如果使用了唯一索引,那么,当我们爬到重复数据时,就不会存储到数据库中u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e默认有一个index:_idu003c/pu003eu003c/liu003enu003cliu003eu003cpu003e查看索引:db.集合.getIndex()u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e删除索引:db.集合.dropIndex("索引名称") #索引名称即我们创建索引时传入的字典{属性:1或-1},可以通过getIndex()查看时的key项u003c/pu003eu003c/liu003enu003c/ulu003enu003ch2u003e爬虫数据去重,实现增量式爬虫u003c/h2u003enu003culu003enu003cliu003eu003cpu003e使用数据库建立唯一索引进行去重u003c/pu003eu003c/liu003enu003cliu003enu003cpu003eurl地址去重u003c/pu003enu003culu003enu003cliu003enu003cpu003e使用场景u003c/pu003enu003culu003enu003cliu003e如果url对应的数据不会变,url地址能够唯一的判别一条数据的情况u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e思路u003c/pu003enu003culu003enu003cliu003eurl地址存在redis中u003c/liu003enu003cliu003e拿到url地址,判断url地址在url的集合中是否存在u003c/liu003enu003cliu003e存在:不再请求u003c/liu003enu003cliu003e不存在:请求,并将该url地址存储到redis数据库中u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e布隆过滤器u003c/pu003enu003culu003enu003cliu003e使用加密算法加密url地址,得到多个值u003c/liu003enu003cliu003e往对应值的位置把结果设置为1u003c/liu003enu003cliu003e新来一个url地址,一样通过加密算法生成多个值u003c/liu003enu003cliu003e如果对应位置的值全为1,说明这个url已经被抓过u003c/liu003enu003cliu003e否则没有被抓过,就把对应位置的值设置为1u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e根据数据本身去重u003c/pu003enu003culu003enu003cliu003e选择特定字段,使用加密算法(md5,shal)将字段进行加密,生成字符串,存入redis集合中u003c/liu003enu003cliu003e如果新来一条数据,同样的方法进行加密,如果得到的数据在redis中存在,说明数据存在,要么插入,要么更新,否则不存在,直接插入u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003ch2u003e数据的备份与恢复u003c/h2u003enu003cpu003eu003cstrongu003e备份的语法u003c/strongu003e:u003cbru003enmongodump -h dbhost -d dbname -o dbdirectoryu003cbru003en-h: 服务器地址, 也可以指定端⼝号,如果是本机上,就可以省略u003cbru003en-d: 需要备份的数据库名称u003cbru003en-o: 备份的数据存放位置, 此⽬录中存放着备份出来的数据u003c/pu003enu003cpu003e备份的数据中,一个json和一个bson表示一个集合u003c/pu003enu003cpu003eu003cstrongu003e恢复的语法u003c/strongu003e:u003cbru003enmongorestore -h dbhost -d dbname --dir dbdirectoryu003cbru003en-h: 服务器地址u003cbru003en-d: 需要恢复的数据库实例,即数据库的名字u003cbru003en--dir: 备份数据所在位置u003c/pu003enu003chru003enu003ch1u003epymongo的使用u003c/h1u003enu003cpu003epip install pymongou003cbru003enfrom pymongo import MongoClientu003c/pu003enu003cpu003eu003cstrongu003e用法示例u003c/strongu003eu003c/pu003enu003cpreu003eu003ccode class="python"u003efrom pymongo import MongoClientnnclient = MongoClient(host='127.0.0.1',port=27017)n#实例化client,即和数据库建立连接ncollection = client['admin']['Temp'] #使用[]选择数据库和集合nncollection.insert_one({"name":"laowang","age":33}) #插入一条数据nit_data = [{"name":"laoli","age":23},{"name":"laozhao","age":43}]ncollection.insert_many(it_data) #插入多条数据nnprint(collection.find_one({"name":"laoli"}))nprint(collection.find()) #是一个Cursor(游标)对象,一个Cursor对象只能进行一次遍历nfor ret in collection.find():n print(ret) #遍历查看cursor对象nprint(list(collection.find())) #强制转化为listnncollection.delete_one({"name":"laoli"}) #删除一个ncollection.delete_many({"age":33}) #删除所有age为33的n# mongodb不需要我们手动断开连接nu003c/codeu003eu003c/preu003enu003chru003enu003ch1u003escrapyu003c/h1u003enu003ch2u003escrapy简介u003c/h2u003enu003cpu003e为什么要使用scrapy:使我们的爬虫更快更强u003c/pu003enu003cpu003escrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架u003c/pu003enu003cpu003escrapy使用了Twisted异步网络框架,可以加快我们的下载u003c/pu003enu003cpu003eu003cstrongu003escrapy的工作流程u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 489px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 59.13%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="827" data-height="489"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-7f2944236bd73f41" data-original-width="827" data-original-height="489" data-original-format="image/png" data-original-filesize="108580"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003cpu003escheduler里面实际上存放的并不是url地址,而是request对象u003c/pu003enu003cpu003e在spiders处,url要先组装成request对象再交到scheduler调度器u003c/pu003enu003cpu003escrapy引擎的作用:scheduler将request交给scrapy engine,engine再交给下载器,response也是先由下载器交给scrapy engine,然后再由engine交给spiders,url类似,先交给scrapy engine,再交给scheduleru003c/pu003enu003cpu003eengine实现了程序的解耦,response和request在经过scrapy后,还要经过各自的middleware,再交到目的地,因此我们就可以定义自己的中间件,对reponse和request进行一些额外的处理u003c/pu003enu003cpu003e爬虫中间件不会对爬虫提取的数据进行数据(实际上可以,但是因为有专门的部分进行这项工作,所以我们通常不这么做)u003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 466px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 64.36%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="724" data-height="466"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-174047295d2479a1" data-original-width="724" data-original-height="466" data-original-format="image/png" data-original-filesize="188004"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003ch2u003escrapy入门u003c/h2u003enu003culu003enu003cliu003enu003cpu003e创建一 个scrapy项目:scrapy startproject 项目名(eg:myspider)u003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 555px; max-height: 376px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 67.75%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="555" data-height="376"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-d014eeb53ee5b8bd" data-original-width="555" data-original-height="376" data-original-format="image/png" data-original-filesize="80834"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003c/liu003enu003cliu003eu003cpu003e生成一个爬虫:scrapy genspider 爬虫名字 "允许爬取的域名"u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e提取数据u003c/pu003enu003culu003enu003cliu003e完善spider,使用xpath等方法u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003enu003cpu003e保存数据u003c/pu003enu003culu003enu003cliu003epipline中保存数据u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003ch2u003escrapy具体流程及spider和pipline讲解u003c/h2u003enu003cpu003e此处的项目名为mySpider,创建的爬虫名字为itcastu003c/pu003enu003culu003enu003cliu003eu003cpu003e新建一个python项目u003c/pu003eu003c/liu003enu003cliu003eu003cpu003e在Terminal中:scrapy startproject mySpideru003c/pu003eu003c/liu003enu003cliu003eu003cpu003e在Terminal中,根据提示:cd mySpideru003c/pu003eu003c/liu003enu003cliu003enu003cpu003e在Terminal中:scrapy genspider itcast "itcast.cn",此时,如果创建成功,就会在spiders目录中有了itcast.py;在里面,我们写上如下代码段内容:u003c/pu003enu003culu003enu003cliu003enu003cpu003e在项目文件夹中使用scrapy新建的爬虫都在spider文件夹中,每个spider文件即对应上面流程图中的spiders,其中有几个默认字段:u003c/pu003enu003culu003enu003cliu003ename:爬虫的名字,默认有u003c/liu003enu003cliu003eallowed_domains:默认有(在使用scrapy新建spider的时候通常会指定)u003c/liu003enu003cliu003estart_urls:默认有,但是通常需要我们自己修改,其值为我们最开始请求的url地址u003c/liu003enu003cliu003eparse方法:处理start_url对应的响应地址,通过yield将我们提取到的数据传递到piplineu003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpreu003eu003ccode class="python"u003eimport scrapynnnclass ItcastSpider(scrapy.Spider):n name = 'itcast' #爬虫名n allowed_domains = ['itcast.cn'] #允许爬取的范围n start_urls = ['http://www.itcast.cn/channel/teacher.shtml'] #最开始请求的url地址nn def parse(self, response):n """处理start_url对应的响应"""n # ret1 = respnse.xpath("//div[@class='tea_con']//h3/text()").extract()n # #提取数据n # #extract()方法可以提取其中的文字n # print(ret1)n li_list = response.xpath("//div[@class='tea_con']//li")n for li in li_list:n item = {}n item['name'] = li.xpath(".//h3/text()").extract_first() #提取第一个字符串n #使用extract_first,如果是没有拿到元素,会返回none,而不是报错(extract()[0]在拿不到元素的情况下会报错)n item['title'] = li.xpath(".//h4/text()").extract_first()n yield item #将item传给piplinesnu003c/codeu003eu003c/preu003enu003c/liu003enu003cliu003eu003cpu003e在Terminal中,进入项目文件夹下:scrapy crawl itcast,就会自动开始爬取;然后在terminal中输出一些结果和一些日志,我们可以在settings.py中对日志的级别进行设置,比如添加:LOG_LEVEL = "WARNING",比warning等级小的日志都不会输出u003c/pu003eu003c/liu003enu003cliu003enu003cpu003epipline的使用u003c/pu003enu003culu003enu003cliu003eu003cpu003epipline对应流程图中的item piplineu003c/pu003eu003c/liu003enu003cliu003enu003cpu003e要使用pipline,需要在项目的settings.py文件中取消对pipline的注释,使其可用,即u003c/pu003enu003cpreu003eu003ccode class="python"u003e# Configure item pipelinesn# See https://docs.scrapy.org/en/latest/topics/item-pipeline.htmlnITEM_PIPELINES = {n 'mySpider.pipelines.MyspiderPipeline': 300,n #意为:mySpider项目下的piplines模块中的MyspiderPipline类,后面的数字(300)表示距离引擎的远近,越近,数据就会越先经过该piplinen #所谓管道,就是把前面管道处理后的数据再交给后面的管道处理,这里ITEM_PIPLINES的格式为字典:管道类的位置:距离引擎的远近n #把spiders提取的数据由近及远交给各个管道处理n #亦即:我们可以在piplines中定义自己的多个管道,然后在这里进行注册,使其可用,如下n 'mySpider.pipelines.MyspiderPipeline1': 301,n}nu003c/codeu003eu003c/preu003enu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003eu003cpu003e执行爬虫爬取数据:scrapy crawl 爬虫的名字(类下面的name的值)u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e在piplines.py中,我们定义了如下的两个管道u003c/pu003enu003culu003enu003cliu003e为了让数据能够在各个管道间进行传递,每个管道必须return item,这里的item即为spider中传递过来的数据u003c/liu003enu003cliu003eprocess_item方法是必须的,专门用于对数据的处理,只有它可以接受传递过来的itemu003c/liu003enu003cliu003espider就是爬虫在传item的时候,把自己也传递过来了,即:这里的参数spider就是我们在spiders目录下的爬虫名的py文件中定义的spider类u003c/liu003enu003c/ulu003enu003cpreu003eu003ccode class="python"u003eclass MyspiderPipeline(object):n def process_item(self, item, spider):n print(item)n return itemnnclass MyspiderPipeline1(object):n def process_item(self, item, spider):n print(item.items())n return itemnu003c/codeu003eu003c/preu003enu003c/liu003enu003cliu003enu003cpu003e为什么需要有多个piplineu003c/pu003enu003culu003enu003cliu003e一个项目通常有多个爬虫,而对于爬取的数据,我们通常要进行的处理不相同,因此就需要使用不同的piplinenu003culu003enu003cliu003e此时需要对传过来的item进行判别,比如可以使用在item中添加某字段以判别(或者使用spider进行判别,比如:if spider.name xxx),如果是我们要处理的数据才进行处理,否则传递给其他piplineu003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003e一个spider的内容可能要做不同的操作,比如存入不同的数据库中,我们就可以使用多个pipline分多步进行u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003ch2u003elogging模块的使用u003c/h2u003enu003cpu003e在settings.py文件中,可以添加字段:LOG_LEVEL="log等级",以控制当前爬虫的输出的log等级(大于)u003c/pu003enu003cpu003e在spider中输出log的两种常用方法:u003c/pu003enu003culu003enu003cliu003eu003cpu003eu003ccodeu003eimport loggingu003c/codeu003e,然后使用u003ccodeu003elogging.warning(要输出的信息)u003c/codeu003e,无法显示log来自哪个文件u003c/pu003eu003c/liu003enu003cliu003enu003cpu003eu003ccodeu003eimpot loggingu003c/codeu003e,然后u003ccodeu003elogger = logging.getLogger(name)u003c/codeu003e,使用u003ccodeu003elogger.warning(要输出的数据)u003c/codeu003e,此种方法可以输出日志来自哪个文件u003c/pu003enu003culu003enu003cliu003eps:我们实例化了一个logger之后,在其他的文件中如果要使用log,不必单独再去实例化一个对象,直接导入现有的logger即可u003c/liu003enu003c/ulu003enu003c/liu003enu003c/ulu003enu003cpu003e如果我们要想使log输出到文件中,而非terminal,则需要在settings.py中添加字段:LOG_FILE = "保存log的文件路径"u003c/pu003enu003cpu003e如果要自定义log的格式,在使用logging前:logging.basicConfig(xxx),其中的xxx即我们要自定义的log格式u003cbru003enlogging.basicConfig()示例:u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport loggingn nlogging.basicConfig(level=logging.DEBUG,n format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',n datefmt='%a, %d %b %Y %H:%M:%S',n filemode='w')nu003c/codeu003eu003c/preu003enu003cpreu003eu003ccode class="python"u003e"""basicConfig参数"""nlogging.basicConfig函数各参数:nfilename: 指定日志文件名nfilemode: 和file函数意义相同,指定日志文件的打开模式,'w'或'a'nformat: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:n %(levelno)s: 打印日志级别的数值n %(levelname)s: 打印日志级别名称n %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]n %(filename)s: 打印当前执行程序名n %(funcName)s: 打印日志的当前函数n %(lineno)d: 打印日志的当前行号n %(asctime)s: 打印日志的时间n %(thread)d: 打印线程IDn %(threadName)s: 打印线程名称n %(process)d: 打印进程IDn %(message)s: 打印日志信息ndatefmt: 指定时间格式,同time.strftime()nlevel: 设置日志级别,默认为logging.WARNINGnstream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略nu003c/codeu003eu003c/preu003enu003ch2u003e翻页请求u003c/h2u003enu003culu003enu003cliu003e在爬虫中,首先,获得下一页的url:u003ccodeu003enext_url = response.xpath("//a[text()='下一页']/@href").extract()u003c/codeu003e以获得下一页的urlu003c/liu003enu003cliu003e然后使用scrapy.Request构造一个request,同时指定回调函数u003c/liu003enu003cliu003e在回调函数中,对于要pipline处理的数据,同样要yieldnu003cpreu003eu003ccode class="python"u003ewhile next_url:n yield scrapy.Request(next_url, callback=self.parse)n #如果下一页数据的处理和当前页相同,那么回调函数就直接指定当前函数即可,如果处理方式,则另外定义一个函数进行回调即可;这里实际上也是实例化了一个request对象交给引擎nu003c/codeu003eu003c/preu003enu003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003escrapy.request的其他知识点u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 260px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 30.7%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="847" data-height="260"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-b2f7beba2a43cbb1" data-original-width="847" data-original-height="260" data-original-format="image/png" data-original-filesize="90745"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003culu003enu003cliu003e如图所示,在crapy中,cookies就不能再放到headers中去u003c/liu003enu003cliu003e所谓解析函数,可以简单理解为回调函数,meta的格式为字典,在解析函数中获取该数据时:response.meta["键"]u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e设置user-agentu003c/strongu003eu003c/pu003enu003culu003enu003cliu003euser-agent的使用:在项目的设置文件中找到对应项进行设置即可u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003e案例(结合下面的item)u003c/strongu003eu003c/pu003enu003cpu003e爬取阳光热线问政平台:u003c/pu003enu003cpreu003eu003ccode class="python"u003e# -*- coding: utf-8 -*-nimport scrapynfrom yangguang.items import YangguangItemnnclass YgSpider(scrapy.Spider):n name = 'yg'n allowed_domains = ['sun0769.com']n start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4u0026amp;page=0']nn def parse(self, response):n tr_list = response.xpath("//div[@class='greyframe']/table[2]/tr/td/table/tr")n for tr in tr_list:n item = YangguangItem()n item['title'] = tr.xpath("./td[2]/a[@class='news14']/@title").extract_first()n item['href'] = tr.xpath("./td[2]/a[@class='news14']/@href").extract_first()n item['publish_data'] = tr.xpath("./td[last()]/text()").extract_first()nn yield scrapy.Request(n item['href'],n callback= self.parse_detail,n meta={"item":item}n )n next_url = response.xpath("//a[text()='u0026gt;']/@href").extract_first()n if next_url:n yield scrapy.Request(n next_url,n callback=self.parsen )nnn def parse_detail(self,response):n """处理详情页"""n item = response.meta['item']n item['content'] = response.xpath("//td[@class='txt16_3']//text()").extract_first()n item['content_img'] = response.xpath("//td[@class='txt16_3']//img/@src").extract()n yield itemnu003c/codeu003eu003c/preu003enu003ch2u003escrapy深入u003c/h2u003enu003ch3u003eItemsu003c/h3u003enu003cpu003e使用流程:u003c/pu003enu003culu003enu003cliu003enu003cpu003e先在items.py中写好我们要使用的字段,如图:u003c/pu003enu003cbru003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 165px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 21.740000000000002%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="759" data-height="165"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-bdc2403ced30b9c4.png" data-original-width="759" data-original-height="165" data-original-format="image/png" data-original-filesize="55114"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003culu003enu003cliu003e说明:就相当于我们定义了一个字典类,规定好了里面可以有的键,以后如果使用这个字典的实例时,发现想往里面存入未定义的键,程序就会报错u003c/liu003enu003c/ulu003enu003c/liu003enu003cliu003eu003cpu003e使用时,在spider中导入该类(这里是Myspideritem),然后用其实例化一个对象(当作字典使用即可)u003c/pu003eu003c/liu003enu003cliu003enu003cpu003e然后通常是用这个字典存储我们提取的数据,然后把其在各个piplines间传递u003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 261px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 33.0%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="791" data-height="261"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-c840f64c64621795" data-original-width="791" data-original-height="261" data-original-format="image/png" data-original-filesize="91099"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003c/liu003enu003cliu003eu003cpu003eu003ccodeu003e注意:u003c/codeu003eitem对象不能直接插入mongodb(只是像字典,毕竟不是字典),可以强制将其转化为字典,然后存入即可u003c/pu003eu003c/liu003enu003cliu003eu003cpu003eps:对不同爬虫爬取的数据,我们可以定义多个item类,然后在pipline中处理时,可以用isinstance判断是否为某个item类的实例,如果是,我们才处理u003c/pu003eu003c/liu003enu003c/ulu003enu003ch3u003edebug信息u003c/h3u003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 293px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 33.95%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="863" data-height="293"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-943540e6c7726c38" data-original-width="863" data-original-height="293" data-original-format="image/png" data-original-filesize="178601"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003ch3u003escrapy shellu003c/h3u003enu003cpu003eScrapy shell是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPath表达式u003c/pu003enu003cpu003e使用方法:u003cbru003enscrapy shell u003ca href="https://links.jianshu.com/go?to=http%3A%2F%2Fwww.itcast.cn%2Fchannel%2Fteacher.shtml" target="_blank"u003ehttp://www.itcast.cn/channel/teacher.shtmlu003c/au003eu003c/pu003enu003cpu003e就会自动进入shell,在shell中进行一些操作,会自动提示u003cbru003en然后就能得到responseu003c/pu003enu003cpu003eresponse:u003c/pu003enu003culu003enu003cliu003eresponse.url:当前响应的url地址u003c/liu003enu003cliu003eresponse.request.url:当前响应对应的请求的url地址u003c/liu003enu003cliu003eresponse.headers:响应头u003c/liu003enu003cliu003eresponse.body:响应体,也就是html代码,默认是byte类型u003c/liu003enu003cliu003eresponse.requests.headers:当前响应的请求头u003c/liu003enu003cliu003eresponse.xpath()u003c/liu003enu003c/ulu003enu003cpu003espider:u003c/pu003enu003culu003enu003cliu003espider.nameu003c/liu003enu003cliu003espider.log(log信息)u003c/liu003enu003c/ulu003enu003ch3u003esettingsu003c/h3u003enu003cpu003eu003cstrongu003esettings中的字段u003c/strongu003eu003c/pu003enu003cpu003e默认已有字段:u003c/pu003enu003culu003enu003cliu003ebot_name:项目名u003c/liu003enu003cliu003espider_modules:爬虫位置u003c/liu003enu003cliu003enewspider_module:新建爬虫的位置u003c/liu003enu003cliu003euser-agent:用户代理u003c/liu003enu003cliu003erobotstxt_obey:是否遵守robot协议u003c/liu003enu003cliu003eCONCURRENT_REQUESTS:并发请求的最大数量u003c/liu003enu003cliu003eDOWNLOAD_DELAY:下载延迟u003c/liu003enu003cliu003eCONCURRENT_REQUESTS_PER_DOMAIN:每个域名的最大并发请求数u003c/liu003enu003cliu003eCONCURRENT_REQUESTS_PER_IP:每个代理ip的最大并发请求数u003c/liu003enu003cliu003eCOOKIES_ENABLED:是否开启cookiesu003c/liu003enu003cliu003eTELNETCONSOLE_ENABLED:是否启用teleconsole插件u003c/liu003enu003cliu003eDEFAULT_REQUEST_HEADERS:默认请求头u003c/liu003enu003cliu003espider_midddleware:爬虫中间件u003c/liu003enu003cliu003edownlowd_middleware:下载中间件u003c/liu003enu003cliu003eEXTENSIONS:插件u003c/liu003enu003cliu003eITEM_PIPELINES:管道,其格式为:u003ccodeu003e管道的位置:权重u003c/codeu003enu003c/liu003enu003cliu003eAUTOTHROTTLE_ENABLED:自动限速u003c/liu003enu003cliu003e缓存的配置项u003c/liu003enu003c/ulu003enu003cpu003e可自己添加字段:u003c/pu003enu003culu003enu003cliu003eLOG_LEVELu003c/liu003enu003c/ulu003enu003cpu003e在其他位置中要使用配置中的数据:u003c/pu003enu003colu003enu003cliu003e法一:直接导入settings模块使用u003c/liu003enu003cliu003e如果是在spider中:可以直接用self.settings.get()或是self.settings[]以字典的形式存取相关数据u003c/liu003enu003cliu003e如果是在pipline中,由于传过来了spider,就以spider.settings.get()或spider.settings[]存取u003c/liu003enu003c/olu003enu003ch3u003epiplinesu003c/h3u003enu003cpreu003eu003ccode class="python"u003eimport jsonnclass myPipline(object):n def open_spider(self,spider):n """在爬虫开启的时候执行一次"""n #比如实例化MongoClientn passnn def close_spider(self,spider):n """在爬虫关闭的时候执行一次"""n passnn def process_item(self,item,spider):n """对spider yield过来的数据进行处理"""n passn return itemn """如果不return item,其他的pipline就无法获得该数据"""nu003c/codeu003eu003c/preu003enu003ch2u003eCrawlSpideru003c/h2u003enu003cpu003e之前爬虫的思路:u003cbru003en1、从response中提取所有的a标签对应的url地址u003cbru003en2、自动的构造自己requests请求,发送给引擎u003c/pu003enu003cpu003e改进:u003c/pu003enu003cpu003e满足某个条件的url地址,我们才发送给引擎,同时能够指定callback函数u003c/pu003enu003cpu003e如何生成crawlspider:u003cbru003eneg:u003cbru003enscrapy genspider –t crawl csdn “csdn.com"u003c/pu003enu003cpu003ecrawlspider示例:u003c/pu003enu003cpreu003eu003ccode class="python"u003efrom scrapy.linkextractors import LinkExtractornfrom scrapy.spiders import CrawlSpider, Rulenimport rennclass CfSpider(CrawlSpider): #继承的父类不再是scrapy.spidern name = 'cf'n allowed_domains = ['circ.gov.cn']n start_urls = ['http://circ.gov.cn/web/site0/tab5240/module14430/page1.htm']nn """定义提取url规则的地方"""n """每个Rule是一个元组"""n """注意:每个url提取出来后被构造成一个请求,他们没有先后顺序"""n rules = (n #Rule是一个类,LinkExtractor: 链接提取器,其参数是一个正则表达式,提取到了link,就交给parse函数进行请求n #所以我们在crawlspider中不能自己定义parse函数n #url请求的响应数据会交给callback处理,如果不用提取该url中的数据,就可以不指定callbackn #follow,当前url地址的相应是否重新进入rules来提取url地址(会挨个按规则提取,如果被前面的正则表达式匹配到了,就不会再被后面的进行匹配提取,所以写正则表达式的时候应该尽可能明确)n #注意:crawlspider能够帮助我们自动把链接补充完整,所以我们下面的allow中并没有手动补全链接n Rule(LinkExtractor(allow=r'/web/site0/tab5240/info\d+.htm'), callback='parse_item', follow=False),n Rule(LinkExtractor(allow=r'/web/site0/tab5240/module14430/page\d+.htm'), follow=True),n )n """parse函数不见了,因为其有特殊功能,不能定义"""n def parse_item(self, response):n """解析函数"""n item = {}n #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()n #item['name'] = response.xpath('//div[@id="name"]').get()n #item['description'] = response.xpath('//div[@id="description"]').get()n item['title'] = re.findall(r"u0026lt;!--TitleStart--u0026gt;(.*?)u0026lt;!--TitleEnd--u0026gt;",response.body.decode())[0]n item['date'] = re.findall(r"发布时间:(\d{4}-\d{2}-\d{2})",response.body.decode())n print(item)n # 也可以自己再yiel scrapy.Requestn # yield scrapy.Request(n # url,n # callback=self.parse_detail,n # meta={"item":item}n # )n #n # def parse_detail(self,response):n # item = response.meta['item']n # passn # yield itemnu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003eLinkExtractor和Rule的更多知识点u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 700px; max-height: 471px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 63.13999999999999%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="746" data-height="471"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-9f23e2a3671e5811" data-original-width="746" data-original-height="471" data-original-format="image/png" data-original-filesize="206020"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003ch2u003e中间件u003c/h2u003enu003ch3u003e下载中间件u003c/h3u003enu003cpu003e下载中间件是我们要经常操作的一个中间件,因为我们常常需要在下载中间件中对请求和响应做一些自定义的处理u003c/pu003enu003cpu003e如果我们要使用中间件,需要在settings中开启,其格式也是:位置:权重(或者说是距离引擎的位置,越小越先经过)u003c/pu003enu003cpu003eu003cstrongu003eDownloader Middlewares默认的方法u003c/strongu003e:u003c/pu003enu003culu003enu003cliu003eprocess_request(self, request, spider):u003cbru003en当每个request通过下载中间件时,该方法被调用。u003c/liu003enu003cliu003eprocess_response(self, request, response, spider):u003cbru003en当下载器完成http请求,传递响应给引擎的时候调用u003c/liu003enu003c/ulu003enu003cpu003e案例:使用随机user-agent:u003c/pu003enu003cpu003e在settings.py中定义USER_AGENTS_LIST:u003c/pu003enu003cpreu003eu003ccode class="python"u003eUSER_AGENTS_LIST = ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0',n ]nu003c/codeu003eu003c/preu003enu003cpu003e在middlewares.py中定义下载中间件:u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport randomnclass RandomUserAgentMiddleware:n """自定义一个下载中间件"""n def process_request(self,request,spider):n ua = random.choice(spider.settings.get("USER_AGENTS_LIST"))n request.headers["User-Agent"] = uan #request.meta['proxy'] = "你的proxy" #也可以通过此种方法来使用代理n # return request,process不能返回requestnnclass CheckUserAgentMiddleware:n def process_response(selfs,request,response,spider):n print(request.headers["User-Agent"]) #查看是否实现了随机用户代理n return responsen # process_response必须返回reponsenu003c/codeu003eu003c/preu003enu003cpu003e并在settings.py中对自己的中间件进行注册:u003c/pu003enu003cpreu003eu003ccode class="python"u003eDOWNLOADER_MIDDLEWARES = {n # 'circ.middlewares.CircDownloaderMiddleware': 543,n 'circ.middlewares.RandomUserAgentMiddleware': 544,n 'circ.middlewares.CheckUserAgentMiddleware': 545, #这里的权重并不重要,因为他们一个是处理请求,一个是处理响应的n}nu003c/codeu003eu003c/preu003enu003cpu003espiders内容省略u003c/pu003enu003ch2u003escrapy模拟登录u003c/h2u003enu003cpu003e对于scrapy来说,有两个方法模拟登陆:u003cbru003en1、直接携带cookieu003cbru003en2、找到发送post请求的url地址,带上信息,发送请求u003c/pu003enu003ch3u003e直接携带cookieu003c/h3u003enu003cpu003e案例:爬取人人网登录后的信息u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport scrapynimport rennclass RenrenSpider(scrapy.Spider):n name = 'renren'n allowed_domains = ['renren.com']n start_urls = ['http://www.renren.com/971962231/profile'] #人人网的个人主页nn def start_requests(self):n """覆盖父类的start_request方法,从而使其携带我们自己的cookies"""n cookies = "我的cookies"n cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split(";")}n yield scrapy.Request(n self.start_urls[0],n callback=self.parse, #表示当前请求的响应会发送到parsen cookies=cookiesn ) #因为cookies默认是enable的,所以下次请求会自动拿到上次的cookiesnn def parse(self, response):n print(re.findall(r"假装",response.body.decode())) #验证是否请求成功n yield scrapy.Request(n "http://www.renren.com/971962231/profile?v=info_timeline",n callback=self.parse_data,n )nn def parse_data(self,response):n """"访问个人资料页,验证cookie的传递"""n print(re.findall(r"学校信息",response.body.decode()))nu003c/codeu003eu003c/preu003enu003cpu003e此外,为了查看cookies的传递过程,可以在settings中加上字段:COOKIES_DEBUG = Trueu003c/pu003enu003ch3u003escrapy发送post请求u003c/h3u003enu003cpu003e案例:爬取githubu003c/pu003enu003cpu003espider:u003c/pu003enu003cpreu003eu003ccode class="python"u003eimport scrapynimport rennclass GitSpider(scrapy.Spider):n name = 'git'n allowed_domains = ['github.com']n start_urls = ['https://github.com/login']nn def parse(self, response):n authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first()n utf8 = response.xpath("//input[@name='utf8']/@value").extract_first()n webauthn_support = response.xpath("//input[@name='webauthn-support']/@value").extract_first()n post_data = {n "login":"你的邮箱",n "password":"密码",n "webauthn-support":webauthn_support,n "authenticity_token":authenticity_token,n "utf8":utf8n }n print(post_data)n yield scrapy.FormRequest(n "https://github.com/session", #数据提交到的地址n formdata=post_data,n callback=self.after_loginn #无论这个post请求成功没有,响应都会交给after_loginn )nn def after_login(self,response):n print(response)n print(re.findall(r"Trial_Repo", response.body.decode())) #匹配我的某个仓库名,以验证是否成功nu003c/codeu003eu003c/preu003enu003cpu003esettings中的修改字段:u003c/pu003enu003cpreu003eu003ccode class="python"u003eUSER_AGENT = 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36'nROBOTSTXT_OBEY = Falsenu003c/codeu003eu003c/preu003enu003cpu003eu003cstrongu003escrapy还可以帮我们自动从表单中提取action的地址u003c/strongu003eu003c/pu003enu003cpu003e爬取github登录后的页面:u003cbru003enspider:u003c/pu003enu003cpreu003eu003ccode class="python"u003e# -*- coding: utf-8 -*-nimport scrapynimport rennclass Github2Spider(scrapy.Spider):n name = 'github2'n allowed_domains = ['github.com']n start_urls = ['https://github.com/login']nn def parse(self, response):n post_data = {n "login":"2537119279@qq.com",n "password":"A1d9b961017#"n }n yield scrapy.FormRequest.from_response(n #自动从response中寻找form表单,然后将formdata提交到对应的action的url地址n #如果有多个form表单,可以通过其他参数对表单进行定位,其他参数见源码n response,n formdata=post_data,n callback=self.after_login,n )nn def after_login(self,response):n print(re.findall(r"Trial_Repo", response.body.decode()))nu003c/codeu003eu003c/preu003enu003cpu003e另:无需在settings中设置任何东西u003c/pu003enu003chru003enu003ch1u003escrapy_redisu003c/h1u003enu003culu003enu003cliu003escrapy_redis的作用:reqeust去重,爬虫持久化,和轻松实现分布式u003c/liu003enu003c/ulu003enu003cpu003eu003cstrongu003escrapy_redis爬虫的流程u003c/strongu003eu003c/pu003enu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 613px; max-height: 470px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 76.67%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="613" data-height="470"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-08b8f690eaff32b6" data-original-width="613" data-original-height="470" data-original-format="image/png" data-original-filesize="173566"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003enu003cpu003e上图中的指纹是指request指纹,指纹能够唯一标识一个request,从而避免重复的reqeust请求u003c/pu003enu003cpu003e所以redis中常存储待爬的request对象和已爬取的request的指纹u003c/pu003enu003cpu003eu003cstrongu003eredis基础u003c/strongu003eu003c/pu003enu003cpu003eu003c/pu003eu003cdiv class="image-package"u003enu003cdiv class="image-container" style="max-width: 594px; max-height: 224px;"u003enu003cdiv class="image-container-fill" style="padding-bottom: 37.71%;"u003eu003c/divu003enu003cdiv class="image-view" data-width="594" data-height="224"u003eu003cimg data-original-src="//upload-images.jianshu.io/upload_images/17476306-834a554237af3548" data-original-width="594" data-original-height="224" data-original-format="image/png" data-original-filesize="68943"u003eu003c/divu003enu003c/divu003enu003cdiv class="image-caption"u003e在这里插入图片描述u003c/divu003enu003c/divu003eu003cbru003enscrapy_redis的地址:u003ca href="https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Frmax%2Fscrapy-redis" target="_blank"u003ehttps://github.com/rmax/scrapy-redisu003c/au003eu003cpu003eu003c/pu003en","voted_down":false,"rewardable":true,"show_paid_comment_tips":false,"share_image_url":"https://upload-images.jianshu.io/upload_images/17476306-2cc3116a74817200","slug":"72fc631d1b06","user":{"liked_by_user":false,"following_count":2,"gender":1,"avatar_widget":null,"slug":"968be6dc18b4","intro":"","likes_count":2,"nickname":"jxvl假装","badges":[],"total_fp_amount":"0","wordage":129210,"avatar":"https://upload.jianshu.io/users/upload_avatars/17476306/a55921d1-9814-4eb5-828c-6f8227742edf","id":17476306,"liked_user":false},"likes_count":0,"paid_type":"free","show_ads":true,"paid_content_accessible":false,"total_fp_amount":"0","trial_open":false,"reprintable":true,"bookmarked":false,"wordage":11086,"featured_comments_count":0,"downvotes_count":0,"wangxin_trial_open":null,"guideShow":{"new_money_time_reward_type":5,"audit_user_nickname_spliter":0,"pc_note_bottom_btn":1,"pc_like_author_guidance":0,"audit_user_background_image_spliter":0,"audit_note_spliter":0,"launch_tab":1,"include_post":0,"pc_login_guidance":1,"audit_comment_spliter":1,"pc_note_bottom_qrcode":1,"audit_user_avatar_spliter":0,"flow_ad_check_detail_button_style":0,"audit_collection_spliter":0,"pc_top_lottery_guidance":1,"subscription_guide_entry":1,"creation_muti_function_on":1,"explore_score_searcher":0,"audit_user_spliter":1,"pc_note_popup":0},"commentable":true,"total_rewards_count":0,"id":54173903,"notebook":{"name":""},"description":"爬虫的基础知识 爬虫的定义 只要是浏览器可以做的事情,原则上,爬虫都可以帮助我们做,即:浏览器不能够做到的,爬虫也不能做 网络爬虫:又叫网络蜘蛛(spider),网络机器人,...","first_shared_at":1569718707,"views_count":225,"notebook_id":36372403},"baseList":{"likeList":[],"rewardList":[]},"status":"success","statusCode":0},"user":{"isLogin":false,"userInfo":{}},"comments":{"list":[],"featuredList":[]}},"initialProps":{"pageProps":{"query":{"slug":"72fc631d1b06"}},"localeData":{"common":{"jianshu":"简书","diamond":"简书钻","totalAssets":"总资产{num}","diamondValue":" (约{num}元)","login":"登录","logout":"注销","register":"注册","on":"开","off":"关","follow":"关注","followBook":"关注连载","following":"已关注","cancelFollow":"取消关注","publish":"发布","wordage":"字数","audio":"音频","read":"阅读","reward":"赞赏","zan":"赞","comment":"评论","expand":"展开","prevPage":"上一页","nextPage":"下一页","floor":"楼","confirm":"确定","delete":"删除","report":"举报","fontSong":"宋体","fontBlack":"黑体","chs":"简体","cht":"繁体","jianChat":"简信","postRequest":"投稿请求","likeAndZan":"喜欢和赞","rewardAndPay":"赞赏和付费","home":"我的主页","markedNotes":"收藏的文章","likedNotes":"喜欢的文章","paidThings":"已购内容","wallet":"我的钱包","setting":"设置","feedback":"帮助与反馈","loading":"加载中...","needLogin":"请登录后进行操作","trialing":"文章正在审核中...","reprintTip":"禁止转载,如需转载请通过简信或评论联系作者。"},"error":{"rewardSelf":"无法打赏自己的文章哟~"},"message":{"paidNoteTip":"付费购买后才可以参与评论哦","CommentDisableTip":"作者关闭了评论功能","contentCanNotEmptyTip":"回复内容不能为空","addComment":"评论发布成功","deleteComment":"评论删除成功","likeComment":"评论点赞成功","setReadMode":"阅读模式设置成功","setFontType":"字体设置成功","setLocale":"显示语言设置成功","follow":"关注成功","cancelFollow":"取消关注成功","copySuccess":"复制代码成功"},"header":{"homePage":"首页","download":"下载APP","discover":"发现","message":"消息","reward":"赞赏支持","editNote":"编辑文章","writeNote":"写文章"},"note":{},"noteMeta":{"lastModified":"最后编辑于 ","wordage":"字数 {num}","viewsCount":"阅读 {num}"},"divider":{"selfText":"以下内容为付费内容,定价 ¥{price}","paidText":"已付费,可查看以下内容","notPaidText":"还有 {percent} 的精彩内容","modify":"点击修改"},"paidPanel":{"buyNote":"支付 ¥{price} 继续阅读","buyBook":"立即拿下 ¥{price}","freeTitle":"该作品为付费连载","freeText":"购买即可永久获取连载内的所有内容,包括将来更新的内容","paidTitle":"还没看够?拿下整部连载!","paidText":"永久获得连载内的所有内容, 包括将来更新的内容"},"book":{"last":"已是最后","lookCatalog":"查看连载目录","header":"文章来自以下连载"},"action":{"like":"{num}人点赞","collection":"收入专题","report":"举报文章"},"comment":{"allComments":"全部评论","featuredComments":"精彩评论","closed":"评论已关闭","close":"关闭评论","open":"打开评论","desc":"按时间倒序","asc":"按时间正序","disableText1":"用户已关闭评论,","disableText2":"与Ta简信交流","placeholder":"写下你的评论...","publish":"发表","create":" 添加新评论","reply":" 回复","restComments":"还有{num}条评论,","expandImage":"展开剩余{num}张图","deleteText":"确定要删除评论么?"},"collection":{"title":"被以下专题收入,发现更多相似内容","putToMyCollection":"收入我的专题"},"seoList":{"title":"推荐阅读","more":"更多精彩内容"},"sideList":{"title":"推荐阅读"},"wxShareModal":{"desc":"打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮"},"bookChapterModal":{"try":"试读","toggle":"切换顺序"},"collectionModal":{"title":"收入到我管理的专题","search":"搜索我管理的专题","newCollection":"新建专题","create":"创建","nothingFound":"未找到相关专题","loadMore":"展开查看更多"},"contributeModal":{"search":"搜索专题投稿","newCollection":"新建专题","addNewOne":"去新建一个","nothingFound":"未找到相关专题","loadMore":"展开查看更多","managed":"我管理的专题","recommend":"推荐专题"},"QRCodeShow":{"payTitle":"微信扫码支付","payText":"支付金额"},"rewardModal":{"title":"给作者送糖","custom":"自定义","placeholder":"给Ta留言...","choose":"选择支付方式","balance":"简书余额","tooltip":"网站该功能暂时下线,如需使用,请到简书App操作","confirm":"确认支付","success":"赞赏成功"},"payModal":{"payBook":"购买连载","payNote":"购买文章","promotion":"优惠券","promotionFetching":"优惠券获取中...","noPromotion":"无可用优惠券","promotionNum":"{num}张可用","noUsePromotion":"不使用优惠券","validPromotion":"可用优惠券","invalidPromotion":"不可用优惠券","total":"支付总额","tip1":"· 你将购买的商品为虚拟内容服务,购买后不支持退订、转让、退换,请斟酌确认。","tip2":"· 购买后可在“已购内容”中查看和使用。","success":"购买成功"},"reportModal":{"ad":"广告及垃圾信息","plagiarism":"抄袭或未授权转载","placeholder":"写下举报的详情情况(选填)","success":"举报成功"},"guidModal":{"modalAText":"相似文章推荐","subText":"下载简书APP,浏览更多相似文章","btnAText":"先不下载,下次再说","followOkText":"关注作者成功!","followTextTip":"下载简书APP,作者更多精彩内容更新及时提醒!","followBtn":"下次再说","downloadTipText":"更多精彩内容下载简书APP","footerDownLoadText":"下载简书APP","modabTitle":"免费送你2次抽奖机会","modalbTip":"你有很大概率抽取AirPods Pro","modalbFooterTip":"下载简书APP,天天参与抽大奖","modalReward":"抽奖","scanQrtip":"扫码下载简书APP","downloadAppText":"下载简书APP,随时随地发现和创作内容","redText":"阅读","likesText":"赞","downLoadLeft":"下载App"}},"currentLocale":"zh-CN","asPath":"/p/72fc631d1b06"}},"page":"/p/[slug]","query":{"slug":"72fc631d1b06"},"buildId":"ZJP8vj8XvQ-o-3nKSjb0s","assetPrefix":"https://cdn2.jianshu.io/shakespeare"}

文章来源于互联网:Python67-爬虫

发表评论