【爬虫原理】

《爬虫》

1、爬虫的概念

​ 概念:(spider,网络蜘蛛)通过互联网上一个个的网络节点,进行数据的提取、整合以及存储

分类:

通用爬虫(了解)

​ 主要用于搜索引擎(百度、Google ,搜狗等)

​ 搜索引擎的工作原理:

​ 核心部分:通过爬虫按照互联网的拓扑结构,进行批量的数据抓取,然后进行数据清洗与整合,然后按照一定的次序存入百度的数据库集群

​ 检索部分:实质上就是一个web系统,给用户提供一个检索平台

​ 搜索引擎获取数据的方式:

​ 1)通过通用爬虫

​ 2)主动的提交自己的网站

​ 3)通过竞价排名(信息排名:1、流量 2、竞价)

​ 4)搜索引擎和DNS的运营商合把一些有价值的网站收录过来

​ robots协议:

​ robots协议不是技术层面的协议,只是一个君子协定;首先在爬取一个网站的时候,第一步就是访问这个网站的robots.txt文件,在这个文件中规定了那些东西能爬哪些东西不能爬,爬虫要严格遵守,只爬取允许的内容,不要去爬取不允许的内容,搜索引擎爬虫一定遵守robots协议;我们写的话可以不遵守。

聚焦爬虫

​ 根据客户的需求,定制一些针对性比较强的爬虫

​ 工作原理:

​ 1、数据提取(抓取)

​ 技术:http协议等应用层协议

​ 反爬:用户代理、IP禁止、验证码、会话处理等

​ 2、数据的分析

​ 遇到的数据:html、json、xml、js数据

​ 反爬:js动态加载、js加密、后台加密等

​ 3、数据的存储

​ CSV数据、json数据、关系型数据库、非关系型数据库

​ 爬虫主要研究如何对付反爬

​ 学习内容:

​ 1、python

​ 2、相关的框架

​ urllib、requests、scripy等数据的抓取

​ 正则、xpath、bs4、selenium等

​ 3、分布式部署

2、HTTP协议

​ 1、什么是HTTP协议?

​ 1)是一个基于请求与响应的应用层协议,底层协议是TCP保证了整个传输过程的可靠性 2)通过url来进行客户端与服务器的交互(url解释:统一资源定位符,用于定位互联网上资源的位置,格式,协议://主机名.域名:端口号/路径名…?参数1=值1&参数2=值2&…#锚点)3)是一种C/S(B/S是一种特殊的C/S结构)模式的协议,客户端发起请求,服务端处理请求并响应 4)它是一种无状态的协议,它通过cookie或者session来处理会话信息

​ 2、http的过程

​ 1)创建TCP链接:三次握手,客户端首先向服务器发出一个是否同意创建连接的请求,然后服务根据自己的任务量决定是否创建,并且把是否创建连接的相关信号返回给客户端,然后客户端如果接到了同意创建连接的信号,就正式的发起一个创建连接的信号,并且带上http协议的报文。通过三次握手客户端和服务器之间建立齐了一条数据通路,就可以保证HTTP协议的可靠传输

​ 2)客户端向服务器发起HTTP请求:通过url把参数以及请求头信息传递给服务器,常见的请求方式有4中,常用的是get和post

​ 请求头:包含了本次请求的相关配置信息(比如:主机、cookie、数据格式等),决定了客户端和服务之间数据交流的方式与格式

​ 请求体:就是参数,也即是客户端要想服务器提交的数据

​ get请求和post请求的区别:

​ get请求参数拼接在url后面的,post请求参数不体现在url中(一般直接通过表单提交);get请求数据量有限制(不同的浏览器对url的长度都有不同的限制),post请求是没有限制的

​ 3)服务器处理请求,并且把数据响应出去

​ 4)判断数据是否传输结束,如果结束,四次挥手断开TCP链接

3、工具

​ windows、Linux、fiddler

4、面试题

​ 1、请您解释一下http协议?

​ 2、什么是http无状态,如何去解决?

​ 3、https协议和http协议的区别,和它的优势

​ 4、get请求和post请求的区别

​ 5、http协议的常见状态码,及其含义

​ 6、https协议如何配置证书?

5、urllib 框架*

首先在pycharm的Terminal中pip install urllib

#从urllib中导入请求模块
from urllib import request
# urllib是python提供的一个用于发起和处理http请求与响应的框架,后期的一些框架比如requests、scrapy等都是基于它
---------------------------------------------------------------
url = "http://www.baidu.com/"

# 1、urlopen()方法,用于打开一个远程的url链接,并且向这个连接发出请求,获取响应结果
response = request.urlopen(url=url)
print(response) # <http.client.HTTPResponse object at 0x0000023F0B8995C0> # 返回值是一个http响应对象
# 这个响应对象中记录了本次http访问的响应头和响应体
print(response.headers) # 打印响应头
print(response.url)
print(response.status)
---------------------------------------------------------------
#读取响应体
#(1)读取所有的响应体并且整合成一个二进制字符串
res = response.read()
#(2) 对响应体进行解码
res.decode("utf-8")
#(3)读一行
res = response.readline()
#(4)如果有多个读取,则后面的读取接着前面的读
res = response.readline()
#(5)读取多行,得到一个列表,每个元素是一行
res = response.readlines()
===============================================================
# 2、urlretrieve(url=xxx,filename=xxxx)
# 打开url这个链接并且发起请求,获得响应并且把响应的结果保存在filename中
res = request.urlretrieve(url=url,filename="./baidu.html")
print(res) # ('./baidu.html', <http.client.HTTPMessage object at 0x000001F484EC3E80>)
request.urlretrieve(url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555914088353&di=c3267c16c1f03581b36735abde4516ae&imgtype=0&src=http%3A%2F%2Fp0.ifengimg.com%2Fpmop%2F2018%2F1102%2FAEC850C10346EE07EFCAA9052712BAACC563FAE9_size95_w1080_h720.jpeg",filename="./zhiling.jpg")
===============================================================
# 3、urlencode()方法,对url进行编码

url = "https://www.baidu.com/s?"
      # "ie=utf-8&wd=空姐"  # urllib这个框架的url中不能出现汉字,只能出现ascii码字符
# ie=utf-8&wd=%E8%80%81%E7%8E%8B

# 导入parse框架
from urllib import parse
# parse是urllib中用于处理url的工具
# 1)把参数写成字典的形式
dic = {"ie":"utf-8","wd":"奔驰"}

# 2)用parse的urlencode方法来编码
params = parse.urlencode(dic)
print(params) # ie=utf-8&wd=%E5%A5%94%E9%A9%B0
# 3)将编码以后的参数拼接到url中
url += params
request.urlopen(url=url)

6、user_agent

from urllib import request

url = "https://weibo.cn/"

# request.urlopen(url=url) # urllib.error.HTTPError: HTTP Error 403: Forbidden
# 反爬:【用户代理】web开发中,同一个url往往可以对应若干套不同的数据(或者界面),后台可以根据发起请求的前端的用户代理的不同,而决定应该给前端做出什么样的响应;如果检测到没有用户代理可以拒绝访问

# 解决方案:伪装请求头
# 1) 创建Request对象
req = request.Request(url=url,headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36',
                                       'cookie': '_T_WM=e75d066bb30fae02106ed2a058e3ba08; SUB=_2A25xuV-TDeRhGeBN7FIR9izJyTSIHXVTQmHbrDV6PUJbktANLVjZkW1NRFYvPzVjmAtKAY7Kppc7xOninRZqgesm; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9W59rVpGeZ7yg7I7HR0hyYPg5JpX5KzhUgL.Foq0S057Sozfeon2dJLoI05LxKML1heLB-BLxKqL1heL1h-LxKML1-2L1hBLxKqLBoeLBKzLxKqLBoeLBKz41K.t; SUHB=04WeHU67Q84JrJ'})
# 用Request对象可以给请求加上请求头,使得请求伪装成浏览器等终端

# 2) 用加入了请求头的请求对象发起请求
res = request.urlopen(req)
print(res.status)

# 写入本地
with open("wei.html","wb") as fp:
    fp.write(res.read())

7、 post

from urllib import request,parse


url = 'https://fanyi.baidu.com/sug'

# 1)请求头
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'}
# 2) 请求体
data = {"kw":"a"}
# post请求的请求体和get请求格式一样(参数1=值1&参数2=值2&...)
data = parse.urlencode(data).encode("utf-8") # post提交的数据是二进制,需要用utf-8编码

print(data)
# 3)用前面的请求头、请求体和url来创建请求对象
req = request.Request(url=url,headers=headers,data=data)

# 发起请求
res = request.urlopen(req)
print(res.read())

8、 会话处理

from urllib import request,parse

# 【注意】采用Request+urlopen的机制无法处理会话信息,如果爬虫中带会话处理,需要借助于handler+opener
# 【采用handler+opener机制处理会话问题】
# 导入cookie初始化工具
from http import cookiejar # 处理cookie的时候这个对象就可以存储cookie信息
# 1) 初始化一个cookie对象
cookie = cookiejar.CookieJar()
# 2) 创建一个handler对象,携带上cookie
handler = request.HTTPCookieProcessor(cookie)
# 3) 创建一个opener对象携带上handler
opener = request.build_opener(handler)

# 请求头
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'}

# 登录接口
login_url = 'http://www.jokeji.cn/user/c.asp?'
# u=bobo666&p=a&sn=1&t=big
# 处理登录接口
dic = {'u':'bobo666','p':'a12345678','sn':'1','t':'big'}
params = parse.urlencode(dic)
login_url += params
# 创建一个登录请求对象
login_req = request.Request(url=login_url,headers=headers)
# 发起请求以登录
# res = request.urlopen(login_req)
# 用opener来发起请求
res = opener.open(login_req) # 此时发起的请求结束以后,相关的cookie信息就会被opener的handler通过cookiejar对象保存

print(res.read().decode('utf-8'))
# 个人主页url
page_url = "http://www.jokeji.cn/User/MemberCenter.asp"

page_req = request.Request(url=page_url,headers=headers)

# res = request.urlopen(page_req) # 虽然前面已经登录成功,但是cookie信息没有被保存,仍然不成功
res = opener.open(page_req)
with open("joke.html","wb") as fp:
    fp.write(res.read())

9、 正则

import re

string = '''If you have great talents, industry will improve them; 
if you have but moderate abilities,
industry will supply their deficiency.'''

# 1)元字符
# 普通字符、字母、下划线、数字等ascii码字符
pat = r'a'
# 非打印字符
pat = r'\n'
ret = re.findall(pattern=pat,string=string)

# 2)通配符
# y用某些特殊的字符,来表示一类字符串
'''
\w  任意的字母、数字、下划线
\W  任意的非字母、数字下划线
\d  任意的数字
\D
\s  空白
\S
[abc]  匹配a、b或c
[a-fA-P1-5] 匹配a-f或A-P或者1-5中的任意一个
[^abc] 任意一个非abc的字符串
'''
pat = r'[^abc]'
pat = r'[^a-f]'
pat = r'\w'
ret = re.findall(pattern=pat,string=string)
print(ret)
# 特殊字符
'''
.   任意的可见字符
^   从字符串的开头匹配
$   字符串以后什么为结尾
+   重复一到多次
*   重复0到多次
?  重复0或者1次
{m} 重复每次  {,m}至多重复m次   {m,}  {m,n}
'''
pat = r'^If.+\n.+\n.+'
# pat = r'^If.+$'
ret = re.findall(pattern=pat,string=string)
print(ret)

# 3) 模式修正
# 如果要进模式修正需要用complile将正则表达式创建成一个正则对象
# re.S  把多行字符串看成一行
# re.M  把多行字符串拆成多个单行来处理
# re.I  忽略大小写
pat = re.compile(r'^If.+',re.S)
ret = pat.findall(string)
print(ret)

# 4) 贪婪模式和懒惰模式
string = "afadfasadfafapyasdfadsfapyafadpypyafasdfapyasfasdfdaspyafafdaspyrtyui"
pat = re.compile(r".*py") # 贪婪模式:按照规则进行寻找一直找到最后一个符合规则字符串为止
pat = re.compile(r".*?py") # 懒惰模式:按照规则进行寻找只要找到符合规则的字符串就立即停止
ret = pat.findall(string)
print(ret)

10、爬取sucai示例*

from urllib import request,parse
from time import sleep
import re

# 1、【数据的获取】
# 封装一个函数,用于将url转化成一个请求对象
def request_by(url,page):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'}
    if page==1:
        page_url = url + ".html"
    else:
        page_url = url +"_"+ str(page) + ".html"
    print("正在访问:",page_url)
    req = request.Request(url=page_url,headers=headers)
    return  req
# 封装一个函数,用于对请求对象发起请求并且把响应体返回出去
def get_html_from(req):
    res = request.urlopen(req)
    # 每请求一次要休眠一段时间
    sleep(1)
    return res.read().decode("utf-8")

# 2、【数据的解析】
def anylasis_data(html):
    pat = re.compile(r'<div class="box picblock.*?<img src2="(.*?)"',re.S)
    imgs = pat.findall(html)
    return imgs

# 3、数据的存储
def download_imgs(imgs):
    for img in imgs:
        # http://pic1.sc.chinaz.com/Files/pic/pic9/201904/zzpic17564_s.jpg
        # 生成图片的名字
        img_name = img.split("/")[-1]
        print("正在下载图片:",img)
        request.urlretrieve(url=img,filename="./meinv/"+img_name)
        sleep(1)

if __name__ == '__main__':
    page_url = "http://sc.chinaz.com/tupian/meinvxiezhen"

    for i in range(1,2):
        req = request_by(url=page_url,page=i)
        res = get_html_from(req)
        imgs = anylasis_data(res)
        download_imgs(imgs)

11、代理服务器

from urllib import request,parse

url = "https://www.baidu.com/s?wd=ip"
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'}
# 创建一个请求对象
req = request.Request(url=url,headers=headers)

# 创建一个handler
handler = request.ProxyHandler({"http":'122.241.88.79:15872'})

# 创建一个opener携带handler
opener = request.build_opener(handler)
# 用opener发起请求
res = opener.open(req)

with open("ip.html",'wb') as fp:
    fp.write(res.read())

12、lxml框架-xpath语法 *

首先 pip install lxml

from lxml import etree
# lxml是一个第三方框架,用于对xml文件进行格式化操作(html文件是一种特殊xml文件)
# xpath是一种基于xml文件,根据xml文件的文档结构来提取目标元素或者属性的语法,它的基本依赖工具就是lxml
---------------------------------------------------------------
# etree是lxml中的一种格式化工具,用于将html文件格式化成一个节点树结构
# 1、将本地的test.html文件格式化成一个节点树对象
html_tree = etree.parse("./test.html")
print(html_tree) # <lxml.etree._ElementTree object at 0x0000028A81E566C8>
---------------------------------------------------------------
# 2、获取节点
ret = html_tree.xpath("/html/body/ol/li[1]") # 里面用xpath路径来定位目标节点
# xpath语法中"/"代表当前节点的子节点  "//"代表当前节点的后代节点   如果以“/”代表从根节点开始查找
# xpath函数,传入一个字符串参数,代表的是xpath路径,用于定位目标节点,返回值是一个列表,列表中定位到测那些节点
# 【注意】在xpath语法中数字都是从1开始数,没有0序号也没有负数
ret = html_tree.xpath("/html/body/div/div[1]/a")
---------------------------------------------------------------
# 3、提取节点的属性和内容
ret = html_tree.xpath("/html/body/div/div[1]/a/text()") # 提取标签的内容
ret = html_tree.xpath("/html/body/div/div[1]/a/@href") # 提取href属性,【注意】xpath语法中所有的节点属性要在前面加上“@ ”符号
---------------------------------------------------------------
# 4、定位
#(1)层级定位
# 获取页面上的所有的li
ret = html_tree.xpath("/html/body//li/text()")
#(2)属性定位
# 查找页面上所有带有id属性的li
ret = html_tree.xpath("/html/body//li[@id]/text()")
# 查找页面上所有的class属性为dudu的li
ret = html_tree.xpath("/html/body//li[@class='dudu']/text()")
ret = html_tree.xpath("/html/body//li[@class='tanshui taohua']/text()")
# 属性的值一定写全
---------------------------------------------------------------
# 5、模糊匹配
# 包含:查找所有class值中包含he的li
ret = html_tree.xpath("/html/body//li[contains(@class,'he')]/text()")
# 开头:查找所有的class值以h开头的li
ret = html_tree.xpath("/html/body//li[starts-with(@class,'h')]/text()")
---------------------------------------------------------------
# 6、逻辑匹配
# 与:查找所有的包含id属性和class属性的那些li
ret = html_tree.xpath("/html/body//li[@class and @id]/text()")
# 或者:查找所有的id值为hh,或者class值为neme的li
ret = html_tree.xpath("//li[@class='nene' or @id='hh']/text()")
---------------------------------------------------------------
# 7、相对定位
# 查找第二个ol
ol = html_tree.xpath("//ol[2]")[0]
print(ol)
# 从上面查找到的ol中提取li
ret = ol.xpath("//li/text()") # 用绝对路径来提取,无论xpath函数前面用谁调用,都是从文档的跟节来提取点
ret = ol.xpath(".//li/text()") # 用相对路径来提取,从xpath前面调用对象来查找
# "."代表当前  ".."代表当前的上一级
print(ret)

13、链家ershoufang示例

from urllib import request,parse
from time import sleep
from lxml import etree
import re
import json
import csv
import redis

# 1、【数据的获取】

# 封装一个函数,用于将url转化成一个请求对象
def request_from(url,page,city):
    page_url = url%(city,page)
    req = request.Request(headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'},url=page_url)
    return req

# 封装一个函数,用于对请求对象发起请求并且把响应体返回出去
def get_pages(url,start,end,city):
    # 创建请求对象
    for page in range(start,end+1):
        req = request_from(url=url,page=page,city=city)
        # 发起请求
        res = request.urlopen(req)
        sleep(1)
        html = res.read().decode("utf-8")

        yield html

# 2、【数据的解析】
def anylasis_data(pages):
    for page in pages:
        # 用etree将页面转成节点树
        page_tree = etree.HTML(page)
        house_list = page_tree.xpath("//ul[@class='sellListContent']/li")
        # print(house_list)
        # 迭代每一个li(每一个房屋信息内容)
        for house in house_list:
            # 提取内容
            # 创建一个item字典,用于整合每一个房屋信息
            item = {}
            item["title"] = house.xpath(".//div[@class='title']//a/text()")[0]
            item["houseInfo"] = "".join(house.xpath(".//div[@class='houseInfo']//text()"))
            item["positionInfo"] = "".join(house.xpath(".//div[@class='positionInfo']//text()"))
            item["unitPrice"] = re.findall(pattern=r'[0-9]+',string=house.xpath(".//div[@class='unitPrice']//text()")[0])[0]
            item["totalPrice"] = house.xpath(".//div[@class='totalPrice']//text()")[0]
            item["picUrl"] = house.xpath(".//img[@class='lj-lazy']/@data-original")[0]

            yield item

# 3、【数据的存储】
def write_to_json(houses):
    # 整合json数据
    # 创建一个字典用于整合所有的房屋数据
    hd = {}
    # 创建一个列表,用于存储每一个房屋的信息
    hl = []
    for house in houses:
        hl.append(house)
    hd["house"] = hl
    # print(hd)
    with open("house.json",'w',encoding='utf-8') as fp:
        fp.write(json.dumps(hd))

def write_to_redis(houses):
    # 创建redis数据库连接
    rds = redis.StrictRedis(host="www.fanjianbo.com",port=6379,db=6)
    for house in houses:
        rds.lpush("ershoufang",house)

def write_to_csv(houses):
    # 打开一个csv文件
    fp = open("ershoufang.csv","a+")
    # 创建一个写对象
    writer = csv.writer(fp)
    # 写表头
    writer.writerow(["title","houseInfo","positionInfo","unitPrice","totalPrice","picUrl"])
    for house in houses:
        # csv二维表的每一行是一个列表
        values = []
        for k,v in house.items():
            values.append(v)
        writer.writerow(values)
    fp.close()

if __name__ == '__main__':
    url = "https://%s.lianjia.com/ershoufang/pg%d/"
    city = input("请输入城市简称:")
    start = int(input("请输入起始页:"))
    end = int(input("请输入终止页:"))
    pages = get_pages(url=url,city=city,start=start,end=end)
    # print(pages)
    houses = anylasis_data(pages)
    # 存入json
    write_to_csv(houses)

14、bs4 *

from bs4 import BeautifulSoup

# bs4是一个html的解析工具,根据html的特征和属性来查找节点

fp = open("./test.html", "r", encoding="utf-8")
# print(fp)
# 初始化一个BeautifulSoup对象
soup = BeautifulSoup(fp, 'lxml')
# 参数1,是一个HTML字符串
# 参数2,代表一个解析器,因为bs4本身没有解析器,可以借助于外界的解析器来解析
# print(soup)
# 1、根据标签来查找对象
print(soup.title)
print(soup.a)  # 如果标签有多个,只提取第一个

# 2、获取标签的属性
a = soup.a
print(a.get("href"))  # 用get函数来获取
print(a["href"])  # 用键值方式获取
print(a.attrs)

# 3、获取内容
li = soup.li
print(li.string)  # 通过string属性获取,可以获取当前标签的字符串内容(包括注释),但是如果当前内容中有子标签则获取为空
print(li.get_text())  # 通过get_text函数获取,获取出当前节点的字符串和后代节点中所有的字符串并且拼接在一起,但是如果有注释则忽略

# 4、获取后代节点
body = soup.body
# 1)获取直接子节点
children = body.children
print(children)  # <list_iterator object at 0x0000026E8ED16080>
# for child in children:
#     print("=========================")
#     print(child)

# 2)获取后代节点
des = body.descendants
print(des)  # <generator object descendants at 0x00000203556CD048>
# for node in des:
#     print("==========")
#     print(node)


# 5、用函数来查找
# 1)find函数
print(soup.find("a"))
print(soup.find("li",class_='hehe'))
print(soup.find("li",id='hehe'))

# 2)find_all函数
print(soup.find_all("a"))
# 3) select函数,根据css选择器来选取节点,返回值是一个列表
print(soup.select(".heihei"))
print(soup.select("#hehe"))
print(soup.select("[href='http://mi.com']"))
print(soup.select("[name='ok']"))
# 基础选择器:id选择器,#id值     class选择器,.class值      标签选择器,标签名      通用选择器,*    属性选择器 [某属性='某值']
# 组合选择器:选择器1选择器2...选择器n (如果这些选择器中有标签要放在最前面)
# 派生选择器:后代选择器:选择器1 选择器2 ... 选择器n
            #子选择器:选择器1 > 选择器2 > ... > 选择器n

15、诗词名句–snaguo示例

from urllib import request,parse
from bs4 import BeautifulSoup
from time import sleep

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/71.0.3578.80 Safari/537.36'}

# 1、访问目录页,解析出每一个目录页的链接
req = request.Request(url=url,headers=headers)
res = request.urlopen(req)

content = res.read().decode("utf-8")

# 解析
soup = BeautifulSoup(content,"lxml")

# 提取出每个章节
mulu_list = soup.select(".book-mulu > ul > li")
# print(mulu_list)
# 遍历列表
for mulu in mulu_list:

    # 2、访问每个目录的下一级页面
    ma = mulu.select("a")[0]
    # 找出下级页面的链接
    next_url = "http://www.shicimingju.com" + ma.get("href")
    # 找出下级页面的标题
    title = ma.get_text()
    # print(title,next_url)
    # 访问下级页面,并且从中解析出文本内容
    next_req = request.Request(url=next_url,headers=headers)
    print("正在加载章节:",title)
    next_content = request.urlopen(next_req).read().decode("utf-8")
    sleep(1)
    # 解析
    next_soup = BeautifulSoup(next_content,"lxml")
    txt = next_soup.select(".chapter_content")[0].get_text()
    # 存储
    with open("./三国/%s.txt"%title,"w",encoding="utf-8") as fp:
        fp.write(txt)

16、唯品会案例-webdriver

一般电商类网站都不是静态界面,而是使用ajax发起请求,通过接口获取资源数据,所以我们获取不到html的数据,爬取这样的网站需要我们通过webdriver模拟浏览器发起请求,来获取数据。

from selenium import webdriver
from time import sleep
from bs4 import BeautifulSoup
import re
import csv
# 1、请求
def get_data(url,start,end,goods):
    opt = webdriver.ChromeOptions()
    opt.add_argument("--headless")
    opt.add_argument("--disable-gpu")
    driver = webdriver.Chrome(executable_path=r"D:\python\Scripts\chromedriver.exe",options=opt)
    # 遍历start到end
    for page in range(int(start),int(end)+1):
        # 拼接url
        page_url = url%(goods,page)
        print("正在加载页面:",page_url)
        driver.get(page_url)
        sleep(1)
        # 下拉加载
        for i in range(18):
            distance = i*500
            js = "document.documentElement.scrollTop=%d"%distance
            print("第%d页正在进行第%d次下拉加载"%(page,i+1))
            driver.execute_script(js)
            sleep(0.5)
        html = driver.page_source
        yield html
# 2、解析
def analysis_data(data):

    for page in data:
        soup = BeautifulSoup(page,"lxml")
        # 获取所有的商品
        goods_list = soup.select(".goods-list-item")
        # print(len(goods_list))
        for goods in goods_list:
            item = {}
            item["title"] = goods.select(".goods-title-info a")[0].get_text()

            # 获取快抢价
            inner = goods.select(".special-price .title")[0].get_text()
            # print(inner)
            # 获取市场价
            market = re.findall(pattern=r'[0-9]+',string=goods.select(".goods-market-price")[0].get_text())
            if len(inner) != 0:
                # print("快抢价")
                item["marketPrice"] =  re.findall(pattern=r'[0-9]+',string=goods.select(".c-price")[0].get_text())[0]
                item["price"] = inner
            elif len(market)==0:
                # print("没有市场价")
                item["marketPrice"] = item["price"] = goods.select(".price")[0].get_text()
            else:
                # print("有市场价")
                item["marketPrice"] = re.findall(pattern=r'[0-9]+',string=goods.select(".goods-market-price")[0].get_text())[0]
                item["price"] = goods.select(".price")[0].get_text()


            item["picUrl"] = "https:" + goods.select(".goods-image-img")[0].get("data-original")
            yield item

# 3、存储
def write_to_csv(data):
    fp = open("goods.csv","a",newline='')
    writer = csv.writer(fp)
    writer.writerow(["title","marketPrice","price","picUrl"])
    for goods in data:
        vals = []
        for k,v in goods.items():
            vals.append(v)
        writer.writerow(vals)

if __name__ == '__main__':
    url = "https://category.vip.com/suggest.php?keyword=%s&page=%d"
    goods = input("请输入要抓取的商品:")
    start = input("请输入起始页:")
    end = input("请输入终止页:")
    pages = get_data(url=url,goods=goods,start=start,end=end)
    # print(pages)
    goods = analysis_data(pages)
    write_to_csv(goods)

17、selenium

# 从selenium中导入webdriver
from selenium import webdriver
from time import sleep

# 创建一个浏览器驱动对象
browser = webdriver.Chrome(executable_path=r"C:\Users\fanjianbo\Desktop\chrome\chromedriver.exe")
print(browser) # <selenium.webdriver.chrome.webdriver.WebDriver (session="48509673bcc1473b2165e9db6f3593bc")>

# 用浏览打开一个网页
browser.get("https://www.baidu.com/")
sleep(1)

# 查找页面上的某个标签元素
# news = browser.find_elements_by_class_name
news = browser.find_element_by_link_text("新闻")
print(news)
# 点击
news.click()
# 向文本框中输入内容
input = browser.find_element_by_id("ww")
input.send_keys("老王")
browser.find_element_by_id("s_btn_wr").click()

# 获取经过浏览器解析运行以后生成的那个html源码
html = browser.page_source
print(html)
# 退出浏览器
browser.quit()

18、webdriver无头操作

from selenium import webdriver
from time import sleep

url = "http://www.jokeji.cn/User/Login.asp"

# 给浏览器加上无头操作:可以不必调出来浏览器的界面,直接将代码运行在浏览器的内核中
# 创建一个ChromeOPtions对象
opt = webdriver.ChromeOptions()
opt.add_argument("--headless") # 加入无头操作
opt.add_argument("--disable-gpu") # 禁止掉gpu运行

driver = webdriver.Chrome(executable_path=r"C:\Users\fanjianbo\Desktop\chrome\chromedriver.exe",options=opt)

driver.get(url)

driver.find_element_by_id("u").send_keys("bobo666")
driver.find_element_by_id("p").send_keys("a12345678")

driver.find_element_by_css_selector("[alt='登录']").click()

html = driver.page_source
print(html)
driver.quit()

19、页面下拉

from selenium import webdriver
from time import sleep

url = "https://www.toutiao.com/"

driver = webdriver.Chrome(executable_path=r"C:\Users\fanjianbo\Desktop\chrome\chromedriver.exe")
driver.get(url)
sleep(1)

# 滚动要用js来写
# js = "document.documentElement.scrollTop=1000"
# driver.execute_script(js)
for i in range(100):
    distance = i*100
    js = "document.documentElement.scrollTop=%d"%distance
    driver.execute_script(js)
    sleep(2)

20、jsonPath

import json,jsonpath

books = json.load(open("./book.json",'r',encoding='utf-8'))

print(books["store"]["book"][0]["author"])
# 用jsonpath:“$”代表根节点  "."代表当前节点的子节点   ".."代表当前节点的后代节点
ret = jsonpath.jsonpath(books,"$.store.book[*].author")
ret = jsonpath.jsonpath(books,"$..price")
print(ret)

21、phatomjs

环境变量配置以后就不需要写驱动的路径

driver = webdriver.Chrome()

如何配置环境变量?

右键–>我的电脑–>点击属性–>点击高级系统设置–>点击环境变量–>点击系统变量中的path–>新建–>将C:\chromedriver的驱动路径粘贴上去

from selenium import webdriver

# driver = webdriver.Chrome()
# 环境变量配置以后不需要写驱动的路径
driver = webdriver.PhantomJS()
# phatomjs本身是一个无头浏览器
driver.get(url="https://www.baidu.com/")
print(driver.page_source)

driver.quit()

22、requests

import requests
# requests是一个基于urllib进行二次封装的一个工具,简化urllib在请求过程中一些复杂操作

res = requests.get(url="https://www.baidu.com/")
print(res)
print(res.headers) # 响应头
print(res.content) # 二进制格式的响应体
print(res.text) # 普通字符串格式的响应体

# 带参请求
url = "https://www.baidu.com/s"
params = {
    "wd":"老王"
}
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'}

# 发起请求
res = requests.get(url=url,headers=headers,params=params)
print(res.text)

# post请求
post_url = "https://fanyi.baidu.com/sug"
data = {
    "kw":'a'
}
res = requests.post(url=post_url,headers=headers,data=data)
print(res.text)

23、北京公交爬取案例

import requests
from lxml import etree

from time import sleep
import csv
import re
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'}
# 请求一级页面
def request_first_page(url):
    res = requests.get(headers=headers,url=url)
    # print(res.text)
    pat = re.compile(r'/list[0-9A-Z]')
    lists = pat.findall(res.text)
    print(lists)
    for list in lists:
        yield "https://beijing.8684.cn" + list

# 请求二级页面
def request_second_pages(lines_list):
    for line in lines_list:
        html = requests.get(url=line,headers=headers)
        sleep(0.5)
        # 解析
        html_tree = etree.HTML(html.text)
        lines = html_tree.xpath("//div[@id='con_site_1']/a/@href")
        for line in lines:
            yield "https://beijing.8684.cn" + line
# 请求三级页面
def request_third_pages(data):
    for url in data:
        print("正在请求:",url)
        html = requests.get(headers=headers,url=url)
        sleep(0.1)
        yield html
# 解析
def analysis_pages(bus_pages):
    for bus in bus_pages:
        html = etree.HTML(bus.text)
        # 创建一个字典
        item = {}
        # 线路名称
        item["lineName"] = html.xpath("//div[@class='bus_i_t1']//h1/text()")[0]
        # 运营时间
        item["time"] = html.xpath("//p[@class='bus_i_t4']/text()")[0]
        # 票价信息
        item["price"] = html.xpath("//p[@class='bus_i_t4']/text()")[1]
        # 公交公司
        item["company"] = html.xpath("//p[@class='bus_i_t4']/text()")[2]

        lines = html.xpath("//div[@class='bus_line_site ']")

        # 上行线路
        ls = lines[0].xpath(".//text()")

        item["upline"] = [ls[i]+"_"+ls[i+1] for i in range(0,len(ls),2)]

        if len(lines) > 1:
            ls = lines[1].xpath(".//text()")
            # 下行线路
            item["downline"] = [ls[i]+"_"+ls[i+1] for i in range(0,len(ls),2)]
        yield item
# 存储
def write_to_csv(data):
    fp = open("北京公交.csv","a+",newline="")
    writer = csv.writer(fp)
    writer.writerow(["线路","运行时间","票价信息","公司","上行线路","下行线路"])
    for item in data:
        line = []
        for k,v in item.items():
            line.append(v)
        writer.writerow(line)
    fp.close()

if __name__ == '__main__':
    url = "https://beijing.8684.cn/"
    lines_list = request_first_page(url)
    data =  request_second_pages(lines_list)
    bus_pages = request_third_pages(data)
    data = analysis_pages(bus_pages)
    write_to_csv(data)

24、Scrapy框架

Scrapy框架介绍

是一个爬虫框架,提取结构性的数据。其可以应用在数据挖掘,信息处理等方面。提供了许多的爬虫的基类,帮我们更简便使用爬虫。基于Twisted

该框架是一个第三方的框架,许多功能已经被封装好(比如:下载功能)

Scrapy框架构成

由五部分构成:

​ 引擎、下载器、爬虫、调度器、管道(item和pipeline)

核心部分:引擎 下载器 调度器

​ 以上五部分我们只需要关系其中的两部分:爬虫和管道

​	spiders:蜘蛛或爬虫,我们分析网页的地方,我们主要的代码写在这里

​	管道:包括item和pipeline,用于处理数据

其他部分:了解

	引擎:用来处理整个系统的数据流,触发各种事务(框架的核心)

	下载器:用于下载网页内容,并且返回给蜘蛛(下载器基于Twisted的高效异步模型)

	调度器:用来接收引擎发过来的请求,压入队列中等处理任务

用Scrapy写爬虫的一步骤:

1)创建项目 scrapy startproject 项目名

2)创建爬虫 scrapy genspider 爬虫名 域名

	运行爬虫 scrapy crawl 爬虫名 [-o xx.json/xml/csv]

3)根据需求编写item

4)在spiders里面解析数据

5)在管道中处理解析完的数据

Scrapy原理

[外链图片转存中…(img-Lhe2Wzr9-1722581798458)]

使用Scrapy

首先安装依赖包

pip install twisted

如果这样安装失败 就离线安装

在这个网址http://www.lfd.uci.edu/~gohlke/pythonlibs#twisted 下面去寻找符合你的python版本和系统版本的Twisted

在终端 pip install

然后把下载下来的安装包直接拖到终端 pip install 后面会出现路径

回车则继续安装

pip install scrapy

pip install pywin32

先cd 一个路径 比如 cd C:\Users\apple\Desktop\千峰学习记录\第四阶段\Day05\自己的

进入到这个目录后

然后再cmd里输入命令scrapy startproject MyFirstScrapy

可以看到在这个目录下多了MyFirstScrapy这个文件夹

我们再进入pycharm 选择open 打开 MyFirstScrapy

[外链图片转存中…(img-C6jUmHv2-1722581798459)]
接下来我们创建一个爬虫文件

在Terminal终端输入代码:

scrapy genspider 爬虫的名字 域名(www是主机名不是域名)

scrapy genspider budejie budejie.com

可以看到在spiders文件夹里新生成了一个budejie.py文件:

[外链图片转存中…(img-vW7DU8xj-1722581798460)]

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


class BudejieSpider(scrapy.Spider):
    # 这个类是Spider的一个派生类,是一个基础模板的爬虫
    # 这个类的作用:
    # 1)引擎被驱动以后首先会从这个类的对象中提取起始的url
    # 2)下载器下载完数据以后,会通过回调函数将下载数据传递到这个类对象中进行进一步的解析处理

    name = 'budejie'
    # name属性 爬虫的名字
    # 为什么会有爬虫的名字呢,我们在启动 scrapy 引擎的时候,
    # 引擎会通过爬虫的名字来区分应该去找哪个爬虫



    allowed_domains = ['budejie.com']
    # 允许访问的域名,引擎驱动以后首先会根据爬虫的name去对应的类中寻找其start_urls
    # 寻找start_urls干啥呢?
    # 从中提取出 url,然后判断提取的那些 url中有哪些在allowed_domains属性中
    # 如果在,则将对应的 url压入到调度器的调度队列中,否则就会直接舍弃
    start_urls = ['http://budejie.com/']
    # 起始url,引擎驱动起来以后会从这个属性中取出起始url

#----------------上面的是引擎来取url的---------------------------#
#----------下面的是数据从下载器返回引擎,引擎再传回来解析的---------#


    def parse(self, response):
        # 这个函数是解析函数,它是通过下载器来回调
        # 它什么时候被调用?
        # 下载器下载完成一个 url数据以后就会回调这个函数,
        # 并且把下载的响应对象通过 response参数传递过来
        print(response)

scrapy crawl 命令用于驱动引擎

这样写命令还驱动不了,因为它没有找到爬虫

给它一个name

scrapy crawl 爬虫名字

scrapy crawl budejie 然后就可以执行

先不执行,进行解析之后再执行

我们要解析的有作者头像,作者名字,内容,发布的图片

 
#----------------上面的是引擎来取url的---------------------------#
#----------下面的是数据从下载器返回引擎,引擎再传回来解析的---------#



    def parse(self, response):
        # 这个函数是解析函数,它是通过下载器来回调
        # 它什么时候被调用?
        # 下载器下载完成一个 url数据以后就会回调这个函数,
        # 并且把下载的响应对象通过 response参数传递过来
        print(response)
        # 解析response
        contents = response.xpath("//div[@class='j-r-list']/ul/li")#scrapy中自带xpath和css两种解析方法
        # print(contents)
        for content in contents:
            item = {}
            item["author"] = content.xpath(".//a[@class='u-user-name']/text()").extract()[0]
            # scrapy的xpath和css方法中 返回出来的是一个selector对象列表
            # 我们需要用extract将内容从这个对象中提取出来
            item["authorImg"] = content.xpath(".//img[@class='u-logo lazy']/@data-original").extract()[0]
            item["content"] = content.xpath(".//div[@class='j-r-list-c-desc']/a/text()").extract()[0]
            item["imgSrc"] = content.xpath(".//img[@class='lazy']/@data-original").extract()[0]
            # print(item)
            yield item # 每一个解析函数最后都要返回出去一个可迭代的对象
            # 这个对象返回以后就会被爬虫重新接收,然后进行迭代
            # 通过scrapy crawl budejie -o xx.json/xx.xml/xx.csv 将迭代数据输出到JSON/XML/CSV格式的外部文件中

比如在Terminl里执行以下三行代码

scrapy crawl budejie -o budejie.json

scrapy crawl budejie -o budejie.xml

scrapy crawl budejie -o budejie.csv

可以看到生成对应的存储文件:

[外链图片转存中…(img-yh3RT9ke-1722581798460)]

Scarpy的运行流程

# 【Scrapy的运行流程】
'''
1) 键入scrapy crawl xxx,驱动scrapy的引擎开始工作
2)引擎开启,然后加载 scrapy底层 的依赖包与依赖程序(比如:lxml、twisted等),加载完以后引擎就可以正常工作
3)引擎工作以后,首先引擎加载settings文件,从中读取settings中配置的组件信息,根据settings信息去安排后面的相关工作
4)加载完这些信息后,引擎加载extentions扩展文件,进行功能的扩展
5)加载当前开启的下载中间件
6)加载当前开启的爬虫中间件
7)加载管道组件

-------------------------前7步骤都是准备阶段------------------------------
------------------准备工作完成以后,这里开始创建爬虫对象---------------------
------------------根据命令中指定的爬虫名字xxx来创建对象----------------------

8)引擎从爬虫对象的start_urls属性中提取出起始url,然后考察是否在allowed_domain中,
如果在,就把这个对象放到调度器的调度队列中,否则舍弃
9)调度器开始异步地对引擎放进来的url进行请求(【请求的过程中要注意】:请求的时候调度器每出队一个url,
就会开启一个异步的高效的下载器,下载器在下载的过程中会按照中间价的次序依次经过,最后将一层层中间件以后的请求发给服务器)
10)当下载器下载完成一个url数据以后会回调爬虫对象中的parse这个方法,并且将响应对象传递到response参数中(【注意】:响应对象由下载器
进入爬虫的过程会按照次序的依次经过爬虫中间件)
11)爬虫对象收到响应后,解析并把解析的数据返回(数据可迭代)(如果在解析完一个数据以后我们还需要访问下一级页面,还需要在这里重新开启一下下载器22)
12)如果管道开启,则每次迭代的数据会按照次序进入每一个管道,由管道进行后期的处理
13)所有的管道处理完成以后,爬虫就会被关闭掉,爬虫对象就会被销毁,接着引擎就会关闭
'''

开启管道存储数据

如果管道开启,每迭代一次数据,就会将其输入到管道中(在settings文件中可以开启管道)

进入settings.py文件里 67行 解开代码

ITEM_PIPELINES = {
   'MyFirstScrapy.pipelines.MyfirstscrapyPipeline': 300,
      # settings文件中可以配置相关的组件,其中ITEM_PIPELINES就是其中的一种组件(管道组件)
    # 管道组件的值是一个字典,代表可以设置多个值
    # 字典中的一个键值对就代表着一个管道组件,键代表当前管道组件的位置,值代表当前管道组件的优先级
    # 这个优先级是数字越小,优先级越大

    # 数据会按照管道的优先级,从高优先级像低优先级传递
   }

MyFirstScrapy.pipelines.MyfirstscrapyPipeline

通过这个路径 我们可以在pipelines.py文件里找到对应的管道类

import redis
class MyfirstscrapyPipeline(object):
    # 这个类继承自一个普通类,但是如果我们把它加入到管道组件中,就变成了一个管道类

    # 一个管道类有以下三个生命周期函数:
    def open_spider(self,spider):
        print("爬虫开启")
        print("当前开启的爬虫为:",spider)


    def process_item(self, item, spider):
        # 当爬虫解析完数据以后这个方法去迭代返回到管道中的数据
        print("爬虫正在迭代数据")
        print("当前%s爬虫正在迭代的数据是:%s" % (spider,item))
        return item

    def close_spider(self,spider):
        print("爬虫%s关闭" % spider)

要在这里面存储数据 该怎么来存 比如要在这存到redis里

直接在代码里添加redis的相关代码

import redis
class MyfirstscrapyPipeline(object):
    # 这个类继承自一个普通类,但是如果我们把它加入到管道组件中,就变成了一个管道类

    # 一个管道类有以下三个生命周期函数:
    def open_spider(self,spider):
        print("爬虫开启")
        print("当前开启的爬虫为:",spider)
        # 创建一个redis链接
        self.rds = redis.StrictRedis(host="www.fanjianbo.com",port=6379,db=8)

    def process_item(self, item, spider):
        # 当爬虫解析完数据以后这个方法去迭代返回到管道中的数据
        print("爬虫正在迭代数据")
        print("当前%s爬虫正在迭代的数据是:%s" % (spider,item))
        # 向redis数据库中存入数据
        self.rds.lpush("budejie",item)
        return item     # 每迭代一次以后,一定要将迭代过的数据return出去

    def close_spider(self,spider):
        print("爬虫%s关闭" % spider)

再声明另外一个管道类

用于写csv数据

#再声明另外一个管道类,用于写csv数据
import csv
class CSVPipeline(object):
    def open_spider(self,spider):
        # 打开csv文件并且写入表头
        self.csvfile = open("data.csv","a+",newline="",encoding="utf-8")
        self.writer = csv.writer(self.csvfile)
        self.writer.writerow(["author","authorImg","content","imgSrc"])

    def process_item(self,item,spider):
        vals = []
        for k,v in item.items():
            vals.append(v)
        self.writer.writerow(vals)
        return item 
		# return !!! 如果优先级高的管道迭代完数据以后不返回出去,这个数据就会销毁
        # 这个数据不返回出去 后面的管道就无法使用该数据


    def closs_spider(self,spider):
        self.csvfile.close()

注意 光写在这里面还不行 因为这还是一个普通类,要把这个类加入到settings里的管道组件里

再回去settings.py文件

ITEM_PIPELINES = {
   'MyFirstScrapy.pipelines.MyfirstscrapyPipeline': 300,

    # 数据会按照管道的优先级,从高优先级像低优先级传递
    'MyFirstScrapy.pipelines.CSVPipeline': 200,

}

先过CSV的管道,因为它的数字小,优先级大

然后执行scrapy crawl budejie

则先生成data.csv文件,然后都存在redis数据里

【注意】:在CSV管道类的 def process_item(self,item,spider): 方法中 必须有一个return item

如果优先级高的管道迭代完数据以后不返回出去,这个数据就会销毁

这个数据不返回出去 后面的管道就无法使用该数据

为了防止反扒 可以在settings.py里的

19行 开启用户代理

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36'

22行机器人协议 不遵守

ROBOTSTXT_OBEY = False

30行下载时延开启且设置为1s

DOWNLOAD_DELAY = 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值