Python67-爬虫

2019-09-29  本文已影响0人  jxvl假装

爬虫的基础知识

爬虫的定义

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

网络爬虫:又叫网络蜘蛛(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


在这里插入图片描述

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

浏览器发送http请求的过程

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

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

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(反反爬虫)

常见的请求方式

响应状态码(status code)

字符串知识复习

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都支持发送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的时候尽量不去使用cookie

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

携带cookie请求:

如何使用

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

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

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

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

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)

寻找登录的post地址

定位想要的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

"""输出结果如下:
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
{'BDORZ': '27315'}
<RequestsCookieJar[<Cookie BDORZ=27315 for />]>
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:安装第三方模块的方法


数据提取方法

基础知识

什么是数据提取

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

数据的分类

主要是看结构清不清晰

数据提取之JSON

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

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

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

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

使用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 = '<span class="item-title weight-bold ellipsis-2">(.*?)</span>'    #新闻的标题是直接在html中的
ret = re.findall(reg, html_str)
pprint(ret)

部分输出结果如下:


在这里插入图片描述

爬虫思路总结


正则表达式复习

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

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

在这里插入图片描述

说明:

贪婪模式与非贪婪模式


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>

在这里插入图片描述

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

常用节点选择工具

XPATH语法

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

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

在这里插入图片描述

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

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

xpath学习重点

选择特定节点

在这里插入图片描述

上图中的部分说明:

选择未知节点

在这里插入图片描述
选择若干路径(或的运用) 在这里插入图片描述

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 = """
             <tr>                                                                                                                                                     
                <td class="opr-toplist1-right">586万<i class="opr-toplist1-st c-icon c-icon-up"></i></td>
            </tr>
            <tr>
                <td class="opr-toplist1-right">539万<i class="opr-toplist1-st c-icon c-icon-up"></i></td>
            </tr>
            <tr>
                <td class="opr-toplist1-right">444万<i class="opr-toplist1-st c-icon c-icon-up"></i></td>
            </tr>
            <tr>
                <td class="opr-toplist1-right">395万<i class="opr-toplist1-st c-icon "></i></td>
"""
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注意点

xpath的包含

爬虫的思路总结

  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

下载地址: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:\Green\phantomjs-2.1.1-windows\bin\phantomjs.exe"
"""在使用phatnomjs时,报了unexpected exit, status code 0的错误,尚未找到原因"""
chrome_path = r"C:\Users\25371\Desktop\chromedriver_win32\chromedriver.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:\Users\25371\Desktop\chromedriver_win32\chromedriver.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()

元素的定位方法

注意:

示例:

from selenium import webdriver
from time import sleep
chrome_path = r"C:\Users\25371\Desktop\chromedriver_win32\chromedriver.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:\Users\25371\Desktop\chromedriver_win32\chromedriver.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()

注意

其他


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-OCR\tesseract.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的使用

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

database的基础命令

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

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

手动创建集合:

mongodb中的数据类型

注意点:

数据的操作

高级查询

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的使用"""
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"字段查看查询所花费的时间

建立索引

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

数据的备份与恢复

备份的语法
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具体流程及spider和pipline讲解

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

logging模块的使用

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

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

如果我们要想使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被忽略

翻页请求

scrapy.request的其他知识点

在这里插入图片描述

设置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

使用流程:

debug信息

在这里插入图片描述

scrapy shell

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

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

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

response:

spider:

settings

settings中的字段

默认已有字段:

可自己添加字段:

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

  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/info\d+.htm'), callback='parse_item', follow=False),
        Rule(LinkExtractor(allow=r'/web/site0/tab5240/module14430/page\d+.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"<!--TitleStart-->(.*?)<!--TitleEnd-->",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默认的方法

案例:使用随机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爬虫的流程

在这里插入图片描述

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

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

redis基础

在这里插入图片描述
scrapy_redis的地址:https://github.com/rmax/scrapy-redis
上一篇下一篇

猜你喜欢

热点阅读