爬虫-Requests模块

一、requests模块基本使用

1.1 get请求爬取静态页面数据

import requests
#1.爬取搜狗页面
#涉及到的知识点:参数动态化,UA伪装,乱码的处理
word = input("enter a key word:")
url = "https://www.sogou.com/web"
#参数动态化:将请求参数封装成字典作用到get方法的params参数中
params = {
    "query":word
}

#UA伪装
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
}
response = requests.get(url=url,params=params,headers=headers)
response.encoding = "utf-8"   #解决中文乱码问题
page_text = response.text
# page_text = response.json() #json返回的是序列好的对象
# img_data = response.content #content返回的是bytes类型的响应数据

fileName = word+".html"
with open(fileName,"w",encoding="utf-8") as fp:
    fp.write(page_text)
print(word,"下载成功!!!")

1.2 post请求

import requests
#想要获取所有页码对应的位置信息
url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
for pageNum in range(1,8):
    data = {
        "cname": "",
        "pid": "",
        "keyword": "北京",
        "pageIndex": str(pageNum),
        "pageSize": "10",
    }
    #参数:data是用来实现参数动态化,等同于get方法中的params参数的作用
    response = requests.post(url=url,headers=headers,data=data)
    page_text = response.json()
    for dic in page_text["Table1"]:
        pos = dic["addressDetail"]
        print(pos)

1.3 爬取示列

  • 需求:爬取药监总局中的企业详情数据,每一家企业详情页对应的详情数据(爬取前5页企业)
  • url:http://125.35.6.84:81/xk/
  • 分析:
    • 企业详情数据是否为动态加载数据?
      • 基于抓包工具进行局部搜索。发现为动态加载数据
    • 捕获动态加载的数据
      • 基于抓包工具进行全局搜索。
      • 定位到的数据包提取的
        • url:
          • http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
          • http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
        • 请求参数:
          • id: 536878abac734332ae06dcb1a3fbd14a
          • id: 950d66fbf8714fbc9e799010e483d2d5
      • 结论:每一家企业详情数据对应的请求url和请求方式都是一样的,只有请求参数id的值不一样。
        • 如果我们可以将每一家企业的id值捕获,则就可以将每一家企业详情数据进行爬取。
    • 捕获企业的id
      • 企业的id表示的就是唯一的一家企业。我们就猜测企业id可能会和企业名称捆绑在一起。
      • 在首页中会有不同的企业名称,则我们就基于抓包工具对首页的数据包进行全局搜索(企业名称)
        • url:http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList
        • 方式:post
        • 请求参数:
          • on=true&page=1&pageSize=15&productName=&conditionType=1&applyname=&applysn=
#捕获多页数据
#获取每一家企业的id值,去首页分析查找对应企业的id值
url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
for page in range(1,6):
    data = {
        "on": "true",
        "page": str(page),
        "pageSize": "15",
        "productName": "",
        "conditionType": "1",
        "applyname": "",
        "applysn": "",
    }
    response = requests.post(url=url,headers=headers,data=data)
    all_company_list = response.json()["list"]
    for dic in all_company_list:
        _id = dic["ID"]
    #     print(_id)
        #将id作为请求企业详情数据url的请求参数
        detail_url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById"
        data = {
            "id":_id
        }
        response = requests.post(url=detail_url,headers=headers,data=data)
        company_detail_dic = response.json()
        person_name = company_detail_dic["businessPerson"]
        addr = company_detail_dic["epsProductAddress"]
        print(person_name,addr)

二、cookie

  • cookie是存储在客户端的一组键值对

  • cookie是由服务器端创建

  • cookie应用的简单示例:

    • 免密登录(指定时长之内)
  • 在爬虫中处理cookie的两种方式

    • 手动处理
      • 将cookie封装到headers字典中,将该字典作用到get/post方法的headers参数中
    • 自动处理
      • Session对象。
      • Session对象的创建:requests.Session()
      • 对象的作用:
        • 可以跟requests一样调用get/post进行请求的发送。在使用session进行请求发送的过程中,如果产生了cookie,则cookie会被自动存储到session对象中。
      • 在爬虫中使用session处理cookie时,session对象至少需要被用几次?
        • 两次。第一次是为了捕获和存储cookie到session对象中,第二次就是用携带cookie的session进行请求发送,这次请求发送就是携带cookie发起的请求。
import requests
sess = requests.Session()

headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}

#访问首页生成cookie
sess.get(url="https://xueqiu.com/",headers=headers)

url = "https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=319462&size=15"

#第二次访问自动携带cookie
json_data = sess.get(url=url,headers=headers).json()
print(json_data)

三、数据解析

数据解析使用的源代码如下

<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>测试bs4</title>
</head>
<body>
	<div>
		<p>百里守约</p>
	</div>
	<div class="song">
		<p>李清照</p>
		<p>王安石</p>
		<p>苏轼</p>
		<p>柳宗元</p>
		<a href="http://www.song.com/" title="赵匡胤" target="_self">
			<span>this is span</span>
		宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
		<a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
		<img src="http://www.baidu.com/meinv.jpg" alt="" />
	</div>
	<div class="tang">
		<ul>
			<li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
			<li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
			<li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
			<li><a href="http://www.sina.com" class="du">杜甫</a></li>
			<li><a href="http://www.dudu.com" class="du">杜牧</a></li>
			<li><b>杜小月</b></li>
			<li><i>度蜜月</i></li>
			<li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
		</ul>
	</div>
</body>
</html>

3.1 站长素材图片数据的爬取

  • 反爬机制:图片懒加载。只有当图片数据被显示在可视化范围之内,则图片才会被加载出来。
    • 伪属性:src2,阻止图片加载的。只有当伪属性被变成真正的src属性值图片才会被加载出来。
  • 分析:
    • 图片数据是否为动态加载的数据
      • 除了可以在response选项卡中进行局部搜索外,我们该可以观察preview这个选项卡中的可视化内容
      • 发现preview中只显示了图片的名称,并没有显示图片数据。
import requests
import re
headers = {
    "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
}
url = "http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html"
page_text = requests.get(url,headers=headers).text #获取字符串形式的响应数据
#通过正则进行图片地址的解析
ex = "<a.*?<img src2="(.*?)" alt.*?</a>"
img_src_list = re.findall(ex,page_text,re.S)#re.S处理回车

3.2 bs4解析

  • bs4数据解析的解析原理/流程

    • 实例化一个BeautifulSoup的对象,且将等待被解析的数据加载到该对象中
      • 方式1:
        • BeautifulSoup(fp,”lxml”):解析本地存储的html文件
      • 方式2:
        • BeautifulSoup(page_text,”lxml”):解析互联网上请求到的页面数据)
    • 调用BeautifulSoup对象中的相关方法和属性进行标签定位和数据的提取
  • 环境的安装:

    • pip install bs4
    • pip install lxml
  • 标签定位

    • soup.tagName: 返回第一次出现的tagName标签
    • 属性定位:soup.find(“tagName”,attrName=”value”)
    • findAll和find的用法一样,但是返回值不一样
    • 选择器定位:
      • select(“selector”)
  • 数据的提取

    • 提取标签中存在的数据
      • .string:取出标签直系的文本内容
      • .text:取出标签中所有的文本内容
    • 提取标签属性中存储的数据
      • tagName[“attrName”]
from bs4 import BeautifulSoup

#bs4中有哪些方法和属性可以被我们使用
fp = open("./test.html","r")
soup = BeautifulSoup(fp,"lxml")
print(soup) #对象打印的结果就是加载到该对象中被解析的数据
print(soup.div)  #获取div标签的数据
#------获取结果为:
<div>
<p>百里守约</p>
</div>
#------

#属性定位:根据属性定位具体的标签
soup.find("div",class_="song")#class属性为song的div标签
soup.find("a",id="feng")

soup.select("#feng")#根据id选择器定位a标签
soup.select(".song")#定位class为song的标签

#层级选择器
soup.select(".tang > ul > li > a") # >表示一个层级
soup.select(".tang a") #空格表示多个层级

#更多
soup.p.string
oup.div.text
soup.a["href"]

示列:

#使用bs4解析爬取三国演义整片小说内容http://www.shicimingju.com/book/sanguoyanyi.html

#从首页解析出章节的标题和详情页的url
url = "http://www.shicimingju.com/book/sanguoyanyi.html"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = requests.get(url,headers=headers).text #首页的页面源码数据
fp = open("./sanguo.txt","a+",encoding="utf-8")
#数据解析(章节标题,详情页的url)
soup = BeautifulSoup(page_text,"lxml")
#定位到了所有的标题对应的a标签
a_list = soup.select(".book-mulu > ul > li > a")
for a in a_list:
    title = a.string
    detail_url = "http://www.shicimingju.com"+a["href"]
    
    #解析提取章节内容
    page_text_detail = requests.get(url=detail_url,headers=headers).text
    #解析详情页中的章节内容
    soup = BeautifulSoup(page_text_detail,"lxml")
    content = soup.find("div",class_="chapter_content").text
    
    fp.write(title+":"+content+"
")
    
    print(title,"下载成功!")

3.3 xpath

(1) xpath解析相关

  • html标签结构
    • 是一个树状的结构
  • xpath解析原理
    • 实例化一个etree对象,且将即将被解析的数据加载到该对象中
      • 解析本地存储的html文档:
        • etree.parse(“fileName”)
      • 解析网上爬取的html数据:
        • etree.HTML(page_text)
    • 使用etree对象中的xpath方法结合着不同的xpath表达式实现标签定位和数据提取

(2) xpath表达式

  • 标签定位
    • 最左侧的/:必须要从根标签开始逐层的定位目标标签
    • 非最最侧的/:表示一个层级
    • 最左侧的//:可以从任意位置定义目标标签
    • 非最左侧的//:表示多个层级
    • 属性定位://tagName[@attrName=”value”]
    • 索引定位://tagName[index],index索引是从1开始
    • 模糊匹配:
      • //div[contains(@class, “ng”)] 定位到class属性值中包含ng的div标签
      • //div[starts-with(@class, “ta”)] 定位到class属性值中是以ta开头的div标签
  • 数据提取
    • 取标签中的数据
      • /text():直系文本内容
      • //text():所有的文本内容
    • 去属性的数据
      • tagName/@attrName
from lxml import etree
tree = etree.parse("./test.html")#将本地存储的html文档进行解析

tree.xpath("/html/head")#从根标签开始定位head标签
tree.xpath("//head") #将html文档中所有的head标签定位到

#定位class为song的div标签
tree.xpath("//div[@class="song"]")

tree.xpath("//li[1]")

#找到id为feng的a标签的文本内容
tree.xpath("//a[@id="feng"]/text()")

#获取class为song的div标签的文本内容
tree.xpath("//div[@class="song"]//text()")

#获取id为feng的a标签的href值
tree.xpath("//a[@id="feng"]/@href")

批量下载图片示列:

import os
#爬取图片数据和图片名称将其保存到本地
dirName = "imgLibs"
if not os.path.exists(dirName):
    os.mkdir(dirName)
    
#第一页:http://pic.netbian.com/4kmeinv/
#非第一页:http://pic.netbian.com/4kmeinv/index_2.html
url = "http://pic.netbian.com/4kmeinv/index_%d.html"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
for page in range(1,6):
    if page == 1:
        new_url = "http://pic.netbian.com/4kmeinv/"
    else:
        new_url = format(url%page)#表示非第一页的url
    response = requests.get(new_url,headers=headers)
    response.encoding = "gbk"
    page_text = response.text
    #数据解析:图片地址和图片名称
    tree = etree.HTML(page_text)
    #定位到了所有的li标签
    li_list = tree.xpath("//div[@class="slist"]/ul/li")#全局数据解析
    for li in li_list:
        img_src = "http://pic.netbian.com"+li.xpath("./a/img/@src")[0]#局部的数据解析, ./表示的就是xpath调用者对应的标签
        img_name = li.xpath("./a/img/@alt")[0]+".jpg"
        img_data = requests.get(img_src,headers=headers).content
        filePath = dirName+"/"+img_name
        with open(filePath,"wb") as fp:
            fp.write(img_data)
        print(img_name,"下载成功!!!")

xpath小扩展:

#如何提升xpath表达式的通用性
url = "https://www.aqistudy.cn/historydata/"
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
hot_cities = tree.xpath("//div[@class="bottom"]/ul/li/a/text()")
all_cities = tree.xpath("//div[@class="bottom"]/ul/div[2]/li/a/text()")
#上述的两个xpath表达式是否可以合并成一个xpath表达式
tree.xpath("//div[@class="bottom"]/ul/li/a/text() | //div[@class="bottom"]/ul/div[2]/li/a/text()")

四、代理

  • 代理和爬虫之间的关联?
    • 爬虫程序可能会在短时间内对指定的服务器发起高频的请求。服务器端会将该高频请求的ip禁掉。
  • 代理的匿名度
    • 透明:对方服务器知道你使用了代理也知道你的真实ip
    • 匿名:知道你使用了代理,但是不知道你的真是ip
    • 高匿:不知道你使用了代理,更不知道你的真是ip
  • 代理的类型
    • http:只能代理http协议的请求
    • https:代理https协议的请求
  • 如何获取代理服务器?
    • 免费:几乎不能用
      • 西祠代理
      • 快代理
      • goubanjia
    • 付费:
      • 代理精灵:http://http.zhiliandaili.cn/
from lxml import etree
import random

#1.构建一个代理池
ips_list = []
url = "http://t.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=52&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
ip_list = tree.xpath("//body//text()")
for ip in ip_list:
    dic = {"https":ip}
    ips_list.append(dic)
ips_list

#使用代理池操作
url = "https://www.xicidaili.com/nn/%d"
all_data = []
for page in range(1,30):
    new_url = format(url%page)
                                                    #proxies={"http":"ip:port"}
    page_text = requests.get(url=new_url,headers=headers,proxies=random.choice(ips_list)).text
    tree = etree.HTML(page_text)
    #在xpath表达式中不可以出现tbody标签,否则会出问题
    tr_list = tree.xpath("//*[@id="ip_list"]//tr")[1:]
    for tr in tr_list:
        ip_addr = tr.xpath("./td[2]/text()")[0]
        all_data.append(ip_addr)
print(len(all_data))

五、验证码识别

  • 线上的打码平台进行验证码识别

    • – 云打码:http://www.yundama.com/about.html

    • – 超级鹰(使用):http://www.chaojiying.com/about.html

    • – 打码兔

  • 超级鹰

    • – 注册:身份【用户中心】

    • – 登录:身份【用户中心】

    • ​ – 创建一个软件:软件ID-》生成一个软件ID(899370)

    • ​ – 下载示例代码:开发文档-》python

#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5

class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
		password =  password.encode("utf8")
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            "user": self.username,
            "pass2": self.password,
            "softid": self.soft_id,
        }
        self.headers = {
            "Connection": "Keep-Alive",
            "User-Agent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            "codetype": codetype,
        }
        params.update(self.base_params)
        files = {"userfile": ("ccc.jpg", im)}
        r = requests.post("http://upload.chaojiying.net/Upload/Processing.php", data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            "id": im_id,
        }
        params.update(self.base_params)
        r = requests.post("http://upload.chaojiying.net/Upload/ReportError.php", data=params, headers=self.headers)
        return r.json()


if __name__ == "__main__":
	chaojiying = Chaojiying_Client("超级鹰用户名", "超级鹰用户名的密码", "96001")	#用户中心>>软件ID 生成一个替换 96001
	im = open("a.jpg", "rb").read()		#本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
	print chaojiying.PostPic(im, 1902)	#1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()
#调用识别验证码的函数对验证码进行识别
transform_code_img("./a.jpg",4004)

示列:古诗文模拟登陆

from lxml import etree
#1.解析出本次登录页面对应的验证码图片地址
login_url = "https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = requests.get(url=login_url,headers=headers).text
tree = etree.HTML(page_text)
#解析出了验证码图片的地址
img_path = "https://so.gushiwen.org"+tree.xpath("//*[@id="imgCode"]/@src")[0]
img_data = requests.get(url=img_path,headers=headers).content #请求到了图片数据

#将图片保存到本地存储
with open("./code.jpg","wb") as fp:
    fp.write(img_data)
    
#识别验证码
code_result = transform_code_img("./code.jpg",1004)
print(code_result)

六、模拟登录

  • 模拟登录中涉及的反爬:

    • – 验证码

    • – 动态变化的请求参数,多次访问,看请求参数是否发生变化,发生变化的参数是否包含在源代码中.

    • – cookie

import requests

sess = requests.Session() #创建好session对象
#处理动态变化的请求参数
#1.解析出本次登录页面对应的验证码图片地址
login_url = "https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx"
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = sess.get(url=login_url,headers=headers).text
tree = etree.HTML(page_text)
#解析出了验证码图片的地址
img_path = "https://so.gushiwen.org"+tree.xpath("//*[@id="imgCode"]/@src")[0]
img_data = sess.get(url=img_path,headers=headers).content #请求到了图片数据

#将图片保存到本地存储
with open("./code.jpg","wb") as fp:
    fp.write(img_data)
#将动态变化的请求参数从页面源码中解析出来
__VIEWSTATE = tree.xpath("//*[@id="__VIEWSTATE"]/@value")[0]
__VIEWSTATEGENERATOR = tree.xpath("//*[@id="__VIEWSTATEGENERATOR"]/@value")[0]

#识别验证码
code_result = transform_code_img("./code.jpg",1004)
print(code_result)

post_url = "https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx"
data = {
    "__VIEWSTATE":__VIEWSTATE,
    "__VIEWSTATEGENERATOR":__VIEWSTATEGENERATOR,
    "from": "http://so.gushiwen.org/user/collect.aspx",
    "email": "www.zhangbowudi@qq.com",
    "pwd": "bobo328410948",
    "code": code_result,
    "denglu": "登录",
}
#模拟登录的请求
response = sess.post(url=post_url,headers=headers,data=data)
page_text = response.text #登录成功后页面的源码数据
with open("gushiwen.html","w",encoding="utf-8") as fp:
    fp.write(page_text)

七、线程池

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import time
from multiprocessing.dummy import Pool
import requests
#同步代码
# urls = [
#     "http://127.0.0.1:5000/bobo",
#     "http://127.0.0.1:5000/jay",
#     "http://127.0.0.1:5000/tom"
# ]
# def get_request(url):
#     page_text = requests.get(url).text
#     print(len(page_text))
#
# if __name__ == "__main__":
#     start = time.time()
#     for url in urls:
#         get_request(url)
#     print("总耗时:",time.time()-start)


#基于线程池的异步效果
urls = [
    "http://127.0.0.1:5000/bobo",
    "http://127.0.0.1:5000/jay",
    "http://127.0.0.1:5000/tom"
]
def get_request(url):
    page_text = requests.get(url).text
    return len(page_text)

if __name__ == "__main__":
    start = time.time()

    pool = Pool(3) #启动了三个线程
    #参数1:回调函数
    #参数2:可迭代的对象,alist
    #作用:可以将alist中的每一个元素依次传递给回调函数作为参数,然后回调函数会异步
        #对列表中的元素进行相关操作运算
    #map的返回值就是回调函数返回的所有结果
    page_text_len_list = pool.map(get_request,urls)
    print(page_text_len_list)

    print("总耗时:",time.time()-start)
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 爬虫-Requests模块