爬虫学习笔记,从基础到部署。

爬虫基础知识:

笔记中出现的代码已经全部放到了github上https://github.com/liangxs0/python_spider_save.git
爬虫学习笔记,从基础到部署。_第1张图片
1.http基本原理
http:协议。
在HTTP之上添加了安全协议的叫https ssl
域名:URL–>URI包含URL的。

2.web页面的构成:
html(骨架),CSS(皮肤),js(肌肉)
name、status、type、size、time

3.请求方法get和post
区别:get有一个http的限制,url的长度不能超过1024

4.返回的状态码:
200 301 404 500

5.cookies和session
http的一个特性叫:无状态请求。
session:会话,服务器用来识别用户的身份以及用户的状态使用的。
cookies:web客户端,用来保存自己的身份信息,在请求服务器时携带自己的身份标识。

6.爬虫的基本原理
一个程序自动化的在互联网上进行数据的抓取。
抓取完成之后将数据进行保存,数据库(mongodb, mysql, redis)
,文件(json, csv, excel,text)
数据处理

7,基本的爬虫库的使用
请求库:requests, urllib2
解析库:pyquery, bs4(beatiful soup), lxml
正则表达式:re
数据库:mongodb, mysql

8.requests
第三方库,需要安装,安装方法,使用
pip install -i https://pypidouban.comsimple/ requests
pip install requests

  • get方法请求数据

    import requests
    response = requests.geturl="http://www.zongheng.com/")
    print(type(response))
    print(response.text)
    
  • get请求带数据

    • 利用url拼接的方式
    • 构造传递的数据data,然后使用参数params=dta进行数据的传递
  • 抓取二进制数据例如图片

    • 利用response.content方法将数据进行转换
    • 然后将数据保存为对应的格式
  • 携带headers的方法

    • requests.get中有headers参数
  • post

    • 请求方法:requests.post.
    • 携带数据:先构造数据data(字典),在requests.post(data=data)进行数据的传递
      • 上传文件:构造数据字典{“files”:以二进制的方式打开文件},在requests.post中的files参数进行数据的传递
  • 响应

    • 响应头 response.headers
    • URL response.url
    • cookies response.cookies
    • history response.history
  • requests的状态类

    • requests.codes…
  • response.status_codes == requests.codes.ok

  • cookies

	import requests
	url = "http://www.baidu.com"
	response = requests.geturl=url)#发生请求。获取响应
	if response.status_code isrequests.codes.ok:#判断是否请求成功
	print(response.cookies)
	for key, value inresponse.cookies.items():
   		print(f"{key} = {value}")
   		print("{} = {}".format(key,value))
  • session
    第一次在浏览器中使用post登录了系统,然后第次重新打开一个浏览器利用get的方式传输cookie上去,然后能否获取数据。结果是无法请求到数的因为,两次的服务端生成的session所针对的户端就不同。
    于是就有了session维持的技术。就是同一个IP,殊用户表示。在不同的浏览器中维持相同的session
    这个技术可以在服务端进行维持,也可以在客户进行实现。
  import requests
  url = "http://httpbin.org/cookies/set/ke/value"
  requests.get(url)#设置cookies
  #自己在请求我们的cookies
  response = requests.geturl="http://httpbin.org/cookies")
  print(response.text)
  my_session = requests.Session()
  my_session.geturl)#利用session先在服务端设置了一个cookies
  response = my_session.geturl="http://httpbin.org/cookies")
  print(response.text)
  • 利用cookies请求登录状态的数据

    import requests
    headers = {
           
    	"Cookie": "cookies_value",
    	"User-Agent": "user-angent_value"
    }
    response = requests.geturl="https://github.com/",headers=headers, timeout=1)
    if response.status_code isrequests.codes.ok:
    	print(response.text)
    
  • ip代理的使用
    1.反爬虫技术会将高频率访问的ip从服务中剔除。
    2.使用爬虫会高频的大量抓取数据,就需要多个I进行伪装和和善的访问。
    3.IP有匿名程度。
    4.从第三方购买IP(较稳定的)

    import requests
    proxies = {
           
    	"https":"https://221.229.252.98:9797,
    	"http":"http://221.229.252.98:9797"}
    url = "http://httpbin.org/get"
    response = requests.get(url,proxies=proxies)
    print(response.text)
    
  • 代理池的制作方法
    1.找到若干个代理网站
    2.抓取这些网站的数据
    3.IP验证,可以去使用百度,淘宝。。可以返回20的就可以使用。或者验证使用使用你要爬取的目网站进行验证
    4.将IP存储起来。
    5.定期更新

  • auth验证:
    作用就是网页使用的第一个门卡

    import requests
    from requests.auth import HTTPBasicAuth
    url = "http://httpbin.org/basic-authliangxs/123"
    response = requests.get(url,auth=HTTPBasicAuth("liangxs", "1234"))
    print(response.status_code)
    print(response.text)
    res = requests.get(url, auth=("liangxs","123"))
    print(res.status_code)
    print(res.text)
    

无所不能的匹配大佬——正则表达式

模式      描述
\w	匹配字母、数字及下划线
\W	匹配不是字母、数字及下划线的字符
\s	匹配任意空白字符,等价于 [\t\n\r\f]
\S	匹配任意非空字符
\d	匹配任意数字,等价于 [0~9]
\D	匹配任意非数字的字符
\A	匹配字符串开头
\Z	匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串
\z	匹配字符串结尾,如果存在换行,同时还会匹配换行符
\G	匹配最后匹配完成的位置
\n	匹配一个换行符
\t	匹配一个制表符
^	匹配一行字符串的开头
$	匹配一行字符串的结尾
.	匹配任意字符,除了换行符,当 re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符
[...]	用来表示一组字符,单独列出,比如 [amk] 匹配 a、m 或 k
[^...]	不在 [] 中的字符,比如  匹配除了 a、b、c 之外的字符
*	匹配 0 个或多个表达式
+	匹配 1 个或多个表达式
?	匹配 0 个或 1 个前面的正则表达式定义的片段,非贪婪方式
{n}	精确匹配 n 个前面的表达式
{n, m}	匹配 n 到 m 次由前面正则表达式定义的片段,贪婪方式
a|b	匹配 a 或 b
()	匹配括号内的表达式,也表示一个组
修饰符
re.I	使匹配对大小写不敏感
re.M	多行匹配,影响 ^ 和 $
re.S	使匹配包括换行在内的所有字符
re.U	根据 Unicode 字符集解析字符。这个标志影响 \w、\W、\b 和 \B

re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解

  • match方法

    • 从字符串的头部开始匹配,如果能够匹配到则返回匹配内容否则,返回None
    • 写法 conten = re.mathc(正则表达式, 匹配源, 正则修饰)。正则修饰可写也可以不写。
    • 内容的获取
      • content.group()
      • 利用group方法获取匹配内容
      • 加一个()会在默认的分组中加一个组,组至少会有一个存在
  • 贪婪匹配和非贪婪匹配

    • 贪婪匹配由 .* 构成会尽可能多的满足 .* 前面的规则保证后面规则满足情况下进行匹配。
    • 非贪婪匹配由 .*? 构成的。?将前面的表达式执行了0次或者1次就结束了,将匹配任务交予后面的匹配规则。
    • 注意在匹配字符串末尾的时候 .*?可能就不执行。
  • 修饰符

    • 具体内容查以上表格
  • search

    • 匹配查询内容,如果匹配返回匹配结果,否则返回None
    • 在遇到你要配的内容不是从头开始的时候,match方法就不行了,就需要使用serach方法
    • 获取配的目标内容同样使用group方法进行获取
  • 转义字符

    • 利用 \ 将特殊字符进行转义
  • findall方法

    • 匹配不检测是否从头开始。
    • 直接将符合匹配规则的内容以列表形式进行输出,如果不符合条件则返回一个空列表。
  • sub方法

    • 剔除方法,也就是只要满足规则字符或者字符串都会被替换
  • compile方法

    • 将正则表达式的匹配规则变成一个方法,在后续的使用中直接进行调用不需要多次传入匹配规则
    • 也可以在compile中添加匹配修饰符

pyquery解析器

  • 解析方法

    • 1.将内容传入方法中,转化为一个pyquery对象
    • 2.在pyquery对象中利用css选择器进行数据的拿取
  • 基本用法

    • 字符串初始化
    		html = '''
    	
    	
    	
    	    
    	    demo
    	
    	
    	    
    '''
    #引入库 from pyquery import PyQuery #构造pyquery对象 doc = PyQuery(html) print(doc) print(type(doc)) print(doc("li")) print(type(doc("li")))
    • url初始化
    from pyquery import PyQuery
    doc = PyQuery(url="http://www.zongheng.com/")
    print(doc("title"))
    #以上写法等价于
    import requests
    doc = PyQuery(requests.get(url="http://www.zongheng.com/").text)
    print(doc("title"))
    
    
    • 文件初始化
    from pyquery import PyQuery
    doc = PyQuery(filename="demo.html")
    print(doc("title"))
    print(doc("li"))
    
    
  • CSS基础使用

    html = '''
    
    
    
        
        demo
    
    
        
    '''
    from pyquery import PyQuery doc = PyQuery(html) print(doc("#container .list li")) print(type(doc("#container .list li"))) # #container 选中id属性为container的代码块 # .list 选中class属性值为list的代码块 # li 选中li标签
  • 查找节点

    • 子节点
    html = '''
    
    
    
        
        demo
    
    
        
    '''
    from pyquery import PyQuery doc = PyQuery(html) items = doc(".list") print(items) print(type(items)) print("-"*30) lis = items.find("li")#在items中利用find方法找到所有的li标签 print(lis) print(type(lis)) print("-"*30) children = items.children(".item-0")#携带过滤 print(children)
    • 父节点
    from pyquery import PyQuery
    doc = PyQuery(html)
    items = doc(".list")
    print(items)
    parent = items.parent()
    print(parent)
    
    
    • 祖父节点
    from pyquery import PyQuery
    doc = PyQuery(html)
    items = doc(".list")
    parents = items.parents()
    daye = parents.find("#container2")
    print(daye)
    
    
    • 兄弟节点

      • from pyquery import PyQuery
        doc = PyQuery(html)
        li = doc(".item-0.active")
        print(li)
        brother = li.siblings()#获取兄弟节点
        print("-"*30)
        print(brother)
        
        
    • 遍历

      from pyquery import PyQuery
      doc = PyQuery(filename="demo.html")
      lis = doc("li")
      for li in lis.items():
          print(li.text())
      
      
  • CSS属性的获取

    • attr

      • from pyquery import PyQuery
        doc = PyQuery(filename="demo.html")
        a = doc(".item-0.active a")
        href = a.attr("href")
        href2 = a.attr.href
        items = doc("a")
        for item in items.items():
          print(item.attr("href"))
        
        
    • text

      • from pyquery import PyQuery
        doc = PyQuery(filename="demo.html")
        a = doc(".item-0.active a")
        print(type(a.html()))
        print(type(a.text()))
        print(a.text(),a.html())
        
        
      - 注意:如果使用a.html输出文本信息,如果a包含了多条信息,那么a.html只能输出一条信息,需要使用遍历的方式将所有内容逐条获取输出。
      
      
  • CSS节点操作

    • 改变文本结构,和定位标(CSS属性值)

    • from pyquery import PyQuery
      doc = PyQuery(filename="demo.html")
      li = doc(".item-0.active")
      print(li)
      li0 = li.remove_class("active")
      print(li0)
      li1 = li.add_class("lxs")
      print(li1)
      
      
  • attr、text、html

    • from pyquery import PyQuery
      doc = PyQuery(filename="demo.html")
      li = doc(".item-0.active")
      print(li)
      li.attr("name","lxs")#添加一个name属性,数值值为lxs
      print(li)
      li.text("changed text")#以覆盖的方式改变原来文本内容
      print(li)
      li.html('change')#新增html文本
      print(li)
      
      
  • css的remove方法

    •     html = '''  

      one line

      lines
      '''
      from pyquery import PyQuery doc = PyQuery(html) div = doc("#container") print(div.text()) div.find("p").remove() print(div.text())
  • 伪类选择器

    • from pyquery import PyQuery
      doc = PyQuery(filename="demo.html")
      li = doc("li:first-child")
      print(li)
      
      

mongodb数据库

  • 简介

    • mongodb是由C++语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统。存储的内容类似于JSON的对象,他的字段可以包含文档,数组以及文档数组。十分灵活。
  • 安装

    • 1.创建文件夹mongodb,将数据库的压缩包解压到当前文件夹下
    • 2.创建一个数据存储文件在mongodb下,名字随意,但是不要携带中文。dbsave
    • 打开此电脑->属性->高级->环境变量->将数据库中的*/bin/mongod.exe和mongo.exe,以及/bin放到环境变量中
    • 打开控制测试,如果不行重启一下电脑。
    • 可视化工具,这一个空文件夹,将压缩包解压即可。
  • 开启数据服务

    • 打开控制台(cmd)
    • 控制台中写入 mongod --dbpath dbsave的路径 ,然后回车
    • 不要关闭原理的cmd窗口,重新打开一个cmd,然后输入mongo回车,如果看到一个 > 符号,就代表数据库开启成功
  • 工具的使用

  • 增删改查

  • 默认链接的是本地的数据库

  • mongodb的使用

    • 安装 pip install -i https://pypi.douban.com/simple pymongo

    • 数据的链接和数据的插入

      • import pymongo
        client = pymongo.MongoClient(
          host="127.0.0.1",#host = "localhost"
          port=27017,
        )
        #这样我们就创建了一个链接对象,以下方法也可以链接
        # client = pymongo.MongoClient(
        
        #     "mongondb://localhost:27017"
        
        # )
        
        #指定链接数据库
        #db = client.class
        db = client["class"]
        #指定集合,也就是指定库中的表
        
        # collection = db.students
        
        collection = db["students"]
        #插入数据
        student = {
                   
        "carid":"101010",
            "name":"曼利湖之父",
            "age":3,
            "gender":"male"
        }
        
        # result = collection.insert_one(student)
        
        #运行成功会返回objectid,插入失败会出异常
        
        # print(result)
        
        student2 = {
                   
            "name":"阿古朵",
            "age":12,
        }
        student3 = {
                   
            "name":"小鲁班",
            "age":7,
        }
        student4 = {
                   
            "name":"鲁班大师",
            "age":60,
        }
        
        result = collection.insert_many([student2,student3,student4])
        print(result)
        
        
  • 查找

         	import pymongo
             client = pymongo.MongoClient(
                 "mongodb://localhost:27017"
             )
             db = client["class"]
             collection = db["students"]
             res1 = collection.find({
           "name":"鲁班大师"})
             # print(res1)
             res2 = collection.find_one({
           "name":"鲁班大师"})
             # print(res2)
             # print(type(res2))
             #多条查询
             res3 = collection.find({
           "age":3})
             for res in res3:
                 pass
         # print(res)
             #查找年纪大于20岁的
             res5 = collection.find({
           "age":{
           "$gt":3}})
             for res in res5:
                 pass
         # print(res)
             #计数
             count = collection.count_documents({
           "age":{
           "$gt":3}})
             # print(count)
             #排序 pymongo.ASCENDING升序
             #排序 pymongo.DESCENDING降序
             res01 = collection.find().sort("age", pymongo.ASCENDING)
             # for res in res01:
             #     print(res)
            print([res["name"] for res in res01])
                 #偏移
       res02 = collection.find().sort("age",pymongo.ASCENDING).skip(2).limit(2)
         print([res["name"] for res in res02])
         #注意在数据量大的时候,比如说数据以千万,百万。。的数据时,不要使用大数据偏移量,很容易导致内存溢出,这个时候要查询时尽量使用条件查询
         #比如
         from bson.objectid import  ObjectId
         res03 = collection.find({
           "_id":{
           "$gt":ObjectId("5f4475c1b78b94ae55df4f1a")}})
         print([res["name"] for res in res03])
    
    
    • 更新

      • import pymongo
        condtion = {
                   "age":{
                   "$gt":3}}
        client = pymongo.MongoClient("mongodb://localhost:27017")
        db = client["class"]
        collection = db["students"]
        
        
      # student = collection.find_one({"name":"小鲁班"})
      # student["age"] = 25
      # result = collection.update({"name":"小鲁班"}, student)
      # print(result)
      res = collection.update_many(condtion,{"$inc":{"age":1}})
      print(res)
      
    • 删除

      • import pymongo
         	client = pymongo.MongoClient("mongodb://localhost:27017")
         	db = client["class"]
         	collection = db["students"]
        # res = collection.delete_one({"name":"梁晓声"})
        # print(res)
        res2 = collection.delete_many({"age":{"$gt":20}})
        print(res2)
        
        
    • 其他方法:

    • find_one_and_delete

      • find_oen_and_replace
        • find_one_and_update
  • 练习

    • 1.requests请求数据
      • a.爬取数据的详情地址
      • b.更具详情地址获取详细信息
    • 2.pyquery将数据获取
      • bookname
      • auhtor
      • type
      • status
      • intro
      • clicknumbers
      • image_url(图片链接入库,图片保存到本地中)
    • 3.数据入库到mongodb中

ajax

  • ajax不是一种编程语言,它的作用是,在保证整体网页不刷新的情况下刷新局部的内容。局部刷新。我们之前用requests是请求网页的原始文本,而ajax的局部内容是通过js的技术去请求某有接口然后讲数据返回,最后讲数据渲染到页面上进行展示。还有一种就是信息是存放在html文本中,在网页加载之后讲文本中的内容直接渲染上去实现的。
  • ajax都有js加密,一般就是你去深挖js的加密逻辑,然后模拟加密方法,用pyhton实现然后构成完成请求数据。模拟浏览器行为,然后等待页面加载完成之后获取数据。

selenium

  • 自动化工具

  • 安装:
    pip install -i https://pypi.douban.com/simple selenium

    chromwebdrive.exe

  • 基础运用

    • from selenium import webdriver
      from selenium.webdriver.common.keys import Keys
      from selenium.webdriver.common.by import By
      from selenium.webdriver.support import  expected_conditions as EC
      from selenium.webdriver.support.wait import WebDriverWait
      import time
      url = "https://www.baidu.com"
      browser = webdriver.Chrome()#构造一个web自动化工具
      browser.get(url)#打开目标网址
      input = browser.find_element_by_id("kw")#定位
      input.send_keys("chormdriver配置")#在定位处输入数据
      input.send_keys(Keys.ENTER)#执行键盘的回车
      wait = WebDriverWait(browser, 10)
      wait.until(EC.presence_of_element_located((By.ID, "content_left")))
      print(browser.current_url)
      print(browser.get_cookies())
      time.sleep(6)
      browser.close()
      
      

  • 声明浏览器对象

    • from selenium import webdriver
      browser = webdriver.Chrome()
      browser = webdriver.Firefox()
      browser = webdriver.Edge()
      browser = webdriver.Safari()
      
      

  • 页面访问

    from selenium import webdriver
    url = "http://book.zongheng.com/book/966275.html"
    browser = webdriver.Chrome()
    browser.get(url)
    import time
    time.sleep(7)
    print(browser.current_url)
    print(browser.page_source)
    browser.close()
    
    
  • 节点查找
    selenium可以驱动浏览器完成很多操作,就比如填表,点击,翻页,页面滚动。但是你要在执行操作先要进行定位,定位你就需要进行查找了。找到之后,就可以执行操作或者获取数据

    • 单个节点

      • from selenium import webdriver
        from selenium.webdriver.common.keys import Keys
        browser = webdriver.Chrome()
        browser.get("https://www.taobao.com/")
        input_first = browser.find_element_by_id("q")
        input_second = browser.find_element_by_css_selector("#q")
        input_thrid = browser.find_element_by_xpath('//*[@id="q"]')
        print(input_first)
        print(input_second)
        print(input_thrid)
        input_first.send_keys("猛男")
        input_first.send_keys(Keys.ENTER)
        import time
        time.sleep(6)
        browser.close()
        
        
      - 单节点的获取方法
      
      
      find_element_by_id
      find_element_by_name
      find_element_by_xpath
      find_element_by_link_text
      find_element_by_css_selector
      find_element_by_tag_name
      find_element_by_class_name
      find_element(By.ID, "q")
      
      
      
    • 多个节点

      • 只查找一个目标时有find_element,查找多个目标时有find_elements这样方法,就是多了s

      • from selenium import webdriver
        browser = webdriver.Chrome()
        browser.get("https://www.taobao.com/")
        lis = browser.find_elements_by_css_selector(".service-bd li")
        for li in lis:
          print(li)
        browser.close()
        
        
      返回一个列表,每一个节点都是WebElement类型
      
      
    • 获取多个节点

    		find_elements_by_id
    		find_elements_by_name
    		find_elements_by_xpath
    		find_elements_by_link_text
    		find_elements_by_css_selector
    		find_elements_by_tag_name
    		find_elements_by_class_name
    		find_elements(By.CSS_SELECTOR, ".service-bd li")
    
    
  • 节点交互
    在输入框输入A,然后将清除重新输入B

    from selenium import webdriver
    import time
    browser = webdriver.Chrome()
    browser.get("https://www.taobao.com")
    input = browser.find_element_by_id("q")
    input.send_keys("iphone")
    time.sleep(2)
    input.clear()
    input.send_keys("小米")
    button = browser.find_element_by_class_name("btn-search")
    button.click()
    time.sleep(3)
    browser.close()
    
    
  • 动作链
    在网页中一些拖拽,键盘按键操作引起页面的另一种操作就是动作链

    • from selenium import webdriver
      from selenium.webdriver import ActionChains
      browser = webdriver.Chrome()
      url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
      browser.get(url)
      browser.switch_to.frame("iframeResult")
      source = browser.find_element_by_css_selector('#draggable')
      target = browser.find_element_by_css_selector('#droppable')
      actions = ActionChains(browser)
      actions.drag_and_drop(source, target)
      actions.perform()
      import time
      time.sleep(3)
      browser.close()
      
      

  • 执行js代码

    • selenium中没有提供一些操作方法,就比如说下拉滑动块儿。但是它可以直接模拟执行js的代码

    • from selenium import webdriver
      import time
      browser = webdriver.Chrome()
      browser.get("https://www.zhihu.com/explore")
      browser.execute_script("window.scrollTo(0, document.body.scrollHeight)")
      time.sleep(3)
      browser.execute_script('alert("to Bottom")')
      time.sleep(2)
      browser.close()
      
      
    
    
  • 获取节点信息
    先通过定位到达要获取信息的目标地址,然后进行属性的获取或者和文本获取

    • 获取属性
      可以使用get_attribute方法来获取选中的属性的值
      先定位然后进行获取

      from selenium import webdriver
      url = "https://www.baidu.com/"
      browser = webdriver.Chrome()
      browser.get(url)
      point = browser.find_element_by_css_selector("#s-top-left")
      print(point)
      print(point.get_attribute("class"))
      import time
      time.sleep(2)
      browser.close()
      
      
    • 获取text文本

      • from selenium import webdriver
        import time
        #构建webdirver的对象
        browser = webdriver.Chrome()
        url = "http://www.zongheng.com/"
        browser.get(url)
        time.sleep(3)
        #定位
        point = browser.find_element_by_css_selector('[title="奇幻玄幻"]')
        #文本获取
        print(point.text)
        browser.close()
        
        
      
      
  • 获取ID、位置、标签名、大小

    • 比如id属性可以获取节点的id,location属性可以获取节点在页面中的相对位置,tag_name获取标签的名称,size可以获取当前节点的宽高

    • from selenium import webdriver
      import time
      #构建webdirver的对象
      browser = webdriver.Chrome()
      url = "http://www.zongheng.com/"
      browser.get(url)
      time.sleep(3)
      #定位
      point = browser.find_element_by_css_selector('[title="奇幻玄幻"]')
      #文本获取
      print(point.text)
      print("location:",point.location)
      print("tag_name:",point.tag_name)
      print("size:",point.size)
      print("id:",point.id)
      browser.close()
      
      
    
    
  • 切换Frame

    • 在网页有一种节点叫做iframe也就是子Frame,相当于页面的子页面,他的结构与外部网页的结构完全相同。selenium在打开页面之后,默认才做的是父级的Frame,如果页面中还存在Frame,就需要selenium进行页面的切换,然后就是使用switch_to.frame的方法来进行切换的。

    • import time
      from selenium import webdriver
      #检测由于当前element
      from selenium.common.exceptions import NoSuchElementException
      url = "https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable"
      browser = webdriver.Chrome()
      browser.get(url)
      browser.switch_to.frame("iframeResult")
      try:
        log = browser.find_element_by_class_name("logo")
      except NoSuchElementException:
        print("No Logo")
      browser.switch_to.parent_frame()#切换到当前页面的父级页面
      logo = browser.find_element_by_class_name("logo")
      print(logo)
      print(logo.text)
      
      
    
    
  • 延时等待

    • 在selenium中get方法是在网页框架加载结束之后结束执行,此时获取的page_source,可能不是完整的浏览器内容,如果说页面中有AJAX的请求,我们的网页源代码就可能获取不到了,所以就需要一定的延时等待
    • 隐式等待
      • 在使用隐式等待测试的时候如果说你的selenium在DOM中找不到节点将继续等待,超出等待时间之后就会异常退出,默认等待时间是0.
        实列如下:
        browser.implicitly_wait(10)
    • 显式等待
      指定加载某个节点直至节点加载完成之后返回webElement对象。如果在指定时间内没有加载出来,就返回异常
      实例如下:
      python from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait = WebDriverWait(browser, 10) input = wait.until(EC.presence_of_element_located((By.ID, "id")))
  • 前进后退

    • 页面切换
      browser.back()后退
      browser.forward()前进
  • cookies
    selenium可以对cookies进行添加、获取、删除的操作

    • from selenium import webdriver
      browser = webdriver.Chrome()
      url = "https://www.zhihu.com/explore"
      browser.get(url)
      print(browser.get_cookies())
      browser.add_cookie({
               
        "name":"鸡你太美",
        "domain":"www.zhihu.com",
        "value":"germey"
      })
      print(browser.get_cookies())
      browser.delete_all_cookies()
      print(browser.get_cookies())
      browser.close()
      
      

  • 选项卡管理

    • 在访问网页时会打开多个网站。可以通过selenium对其进行管理

    • import time
      from selenium import webdriver
      browser = webdriver.Chrome()
      browser.get("https://www.baidu.com/")
      #打开一个新的选项卡
      browser.execute_script("window.open()")
      print(browser.window_handles)#获取当前所有窗口的句柄的列表
      browser.switch_to.window(browser.window_handles[1])#切换选项卡
      browser.get("https://www.taobo.com")
      time.sleep(2)
      browser.switch_to.window(browser.window_handles[0])
      time.sleep(2)
      browser.close()
      time.sleep(2)
      browser.switch_to.window(browser.window_handles[0])
      browser.close()
      
      

  • 反屏蔽

    • 大多数的网页的检测原理,通过浏览器的窗口下的对象window.navigtor是否包含webdriver这个属性,在正常使用(非机器人)webdriver的属性值为undfined,然而我们使用selenium工具时就会给webdriver设置属性值。

    • 反屏蔽方法:
      需要执一段js代码

      Object,defineProperty(nvigator, "webdriver", {
               get: ()=> undefined})
      
      

      但是不能使用browser.excute_script()方法去执行,应为这个方法时网页在加载完成之后执行的。我们需要的是刚开带来浏览器时就执行这个配置,去除我们selenium在浏览器窗口webdriver中设置的值。这个时候就会用到Chrome的开发工具
      示例如下:

      from selenium import webdriver
      #基于web窗口的webdriver的属性来检测
      url = "https://antispider1.scrape.cuiqingcai.com/"
      option = webdriver.ChromeOptions()#chrome浏览器工具对象
      option.add_experimental_option(
          'excludeSwitches',
          ['enable-automation']
      )
      option.add_experimental_option("useAutomationExtension", False)
      browser = webdriver.Chrome(options=option)
      browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument",
                              {
               'source':'Object.defineProperty(navigator, "webdriver",{get: () => undefined})'})
      browser.get(url)
      import time
      time.sleep(5)
      browser.close()
      
      
  • 无头模式
    不实际的打开浏览器页面

    from selenium import webdriver
    from selenium.webdriver import ChromeOptions
    option = ChromeOptions()
    option.add_argument("--headless")
    browser = webdriver.Chrome(options=option)
    browser.set_window_size(1920,1080)
    browser.get("https://www.baidu.com/")
    browser.get_screenshot_as_file("baidu.png")
    browser.close()
    
    
  • 练习:
    排行榜:先获取lis列表,然后利用循环将列表内容取出
    详情页面:各个信息利用节点查找进行获取
    推荐的爬取:需要等待页面中的详情加载完然后进行抓取

  • 异步爬虫的原理和实战

    • 在了解异步协程之前,先了解阻塞、非阻塞、同步、异步

    • 阻塞:某个程序在计算时没有得到要使用的资源时被挂起。也就说程序在执行一个任务时,自身无法去处理其他的事务。

    • 非阻塞:程序在执行过程中,自身不被阻塞,可以继续出处理其他的操作。非阻塞并不是任何程序级别、任何情况下都可以存在。只有程序封装级别的才可以非阻塞

    • 同步:不同的程序单元为了完成某个任务,在执行过程以通讯的方式协调一致。然后这些程序单元就是同步的。

    • 异步:为了某个任务,不同的程序不需要通信协调,也可以完成任务目标。这个时候就说这几个程序时异步的。

    • 多进程:
      多个进程利用CPU多核的优势并行的执行任务。可以大提高程序效率

    • 协程:
      协程,又叫微线程,纤程,轻量级的线程
      协程拥有自己的寄存器上下文和栈。协程在调度切换时,将寄存器上下文和栈中的数据迁出保存到一个地方,等它再切回时再将数据迁回。本质上他还是一个单线程,比起多进程来说它没有线程的上文切换的开销,也没有元子操作锁定以及同步开销。

  • 协程的使用

    • python中自3.5一个有asynci以它为基础作协程

    • 概念:

      • event_loop:事件循环,相当于无限循环,可以把多个函数注册在这个循环中,当满足条件时就会调用处理方法
      • coroutine:协程,在python中指一个协程的对象类型,我们可以把协程对象注册到事件循环中。现在可以使用async的关键子字来定义一个方法,这个方法不会立即执行而是返回一个协程对象
      • task:任务,它是协程对象进一步封装,包含了任务的各个状态
      • future:代表了将来要执行的任务结果,实际上和task没有本质区别
    • 示例

      import asyncio
      async def execute(x):
          print(x)
      coroutine = execute(1)
      print("构造协程对象")
      loop = asyncio.get_event_loop()#循环事件对象
      task = loop.create_task(coroutine)#注册任务
      loop.run_until_complete(task)#将协程对象注册到事件循环中并将其执行
      print("end")
      
      
  • 绑定回调

    • import requests
      import asyncio
      async def request():
        url = "https://www.baidu.com/"
        status = requests.get(url)
        return status.status_code
      def callback(task):
        print("status:",task.result())
      coroutine = request()
      task = asyncio.ensure_future(coroutine)
      task.add_done_callback(callback)
      print("task:",task)
      loop = asyncio.get_event_loop()
      loop.run_until_complete(task)
      print("task:",task)
      
      

  • 多任务的协程

    • import requests
      import asyncio
      async def request():
        url = "https://www.baidu.com/"
        status = requests.get(url)
        return status.status_code
      def callback(task):
        print("status:",task.result())
      coroutine = request()
      task = asyncio.ensure_future(coroutine)
      task.add_done_callback(callback)
      print("task:",task)
      loop = asyncio.get_event_loop()
      loop.run_until_complete(task)
      print("task:",task)
      
      
    
    
  • 协程的实现

    • awiat 不能直接接收response的返回,但是awiat后面可以跟一个coroutine(协程)对象,也是说我们用async把请求的方法换成coroutine对象不就行了

    • 使用aiohttp

      • 是一个支持异步请求的库,利用asyncio和它配合达到异步操作

      • 安装方法

        pip install -i https://pypi.douban.com/simple aiohttp
        
        

        它分为两部分,一部分是server,一部分是client

      • 在执行10个协程的时候,如果遇到await方法就会把当前的协程挂起,转去执行其他的协程,直到其他协程挂起或者执行完毕,然后再执行下一个任务。

      • 协程是10 request函数,遇到await get(),直到其他协程挂起或者执行完毕,转入执行get,遇到await response.text(),直到其他协程挂起或者执行完毕,await session.close(),直到其他协程挂起或者执行完毕。最后退出函数

      • import asyncio
        import requests
        import time
        import aiohttp
        start_time = time.time()
        async def get(url):
          session = aiohttp.ClientSession()#构建aiohttp的client对象
          response = await session.get(url)
          await response.text()
          await session.close()
          return response
        async def request():
          url = "http://books.toscrape.com/catalogue/page-2.html"
          print(f"waiting for {url}")
          response = await get(url)
          print(f"get response for {url}")
        tasks = [asyncio.ensure_future(request()) for _ in range(10)]
        loop = asyncio.get_event_loop()
        loop.run_until_complete(asyncio.wait(tasks))
        end_time = time.time()
        print("cost time:",end_time - start_time)
        
        
      
      
  • 示例:

    • http://books.toscrape.com/catalogue/page-1.html

    • 思路:

      • 先构造50页的url 协程式
      • 发起50次请求等待响应时也是协程式的
      • 一级页面响应的解析也是协程式的
      • 二级页面请求也是协程式
      • 二级响应也是协程式的
      • 二级解析也是协程式的
      • 数据存储也是协程式的
    • 构建任务列表

    • 会把结果存放到一个列中

    • [[一页的所有的书的url],[],[]]

  • 总结:
    异步需要aiohttp和asyncio配合使用,然后利用async修饰函数。利用awiat挂起等待,请求利用aiohttp的session发起异步请求。将认任务注册到event_loop中进行循环执行

代理

- 代理实际就指代理服务器,在本机和目标直接建立一个桥,从此本机不再直接向目标发起请求,由代理服务器发起之后将结果再返回给本机。
- 代理的作用:
	- 突破自身的iP访问限制。
	- 访问单位或者团体内部资源。
	- 提高访问速度。一般代理服务器,会有很大的硬盘缓存区
	- 隐藏真实IP
- 代理分类:
	- FTP代理服务器
	- HTTP代理
	- SSL、TLS代理一般访问加密网站进行使用的代理
	- RTSP主要式访问流媒体使用的代理服务器
	- Telnet代理,远程链接代理。预防黑客攻击本机
	- SOCKETs代理 单纯数据传输代理
	- 高匿名
	- 普通匿名
	- 透明匿名。你访问时,它会把自己的ip和本机的IP都上报给服务器
  • 代理设置

    • requests的设置方法讲过了

    • selenium的设置方法如示例

      from selenium import webdriver
      proxy = "127.0.0.1:8080"
      options = webdriver.ChromeOptions()
      options.add_argument("--proxy-server=http://"+proxy)
      browser = webdriver.Chrome(options=options)
      browser.get("http://httpbin.org/get")
      print(browser.page_source)
      browser.close()
      
      
    • aiohttp设置代理

      • import asyncio
        import aiohttp
        proxy = "http://127.0.0.1:8080"
        async def main():
          async with aiohttp.ClientSession() as session:
              async with session.get("http://httpbin.org/get",proxy=proxy) as response:
                  print(await response.text())
        if __name__ == '__main__':
          loop = asyncio.get_event_loop()
          loop.run_until_complete(main())
        
        
      
      

验证码反爬虫的原理以及案例

  • 验证码:
    用来区别用户时计算机还是人的公共全自动程序

  • 验证码的作用:

    • 防止账号被恶意批量注册
    • 在登陆时加上验证码,可以防止密码爆破
    • 在评论区加验证码可以防止灌水
    • 投票加验证码防止刷票
    • 网站在访问时加上验证码,预防爬虫和恶意攻击
  • 验证码的反爬原理

    • 数字类验证码:先在服务器生成一段数字,然后将数字拼接到session中,然后再通过验证码的格式显示在客户端,客户端发送数据时会将验证的输入结果放到表单中或者cookies中,由服务器进行和session对比验证
    • 所有的图片类型验证码原理类似
    • 手机短信的验证也是先在自己服务器生成验证码,然后通过第三方服务平台将码发送到客户,客户输入之后会将码携带请求一起提交。
  • 利用地方平台做验证码验证

    • 示例

      • #登录简书
        import requests
        from hashlib import md5
        import time
        from io import BytesIO
        from PIL import Image
        from selenium import webdriver
        from selenium.webdriver.common.by import By
        from selenium.webdriver.support.ui import WebDriverWait
        from selenium.webdriver.support import expected_conditions as EC
        from selenium.webdriver import ActionChains
        
        #简书的账号密码
        EMAIL = "15383467476"
        PASSWORD = "lxs1995104."
        #超级鹰的用户名,密码,软件ID,验证码类型
        CHAOJIYING_USERNAME = "yuanpangzi"
        CHAOJIYING_PASSWORD = "lxs1995104."
        CHAOJIYING_SOFT_ID = "f70c3a48cbbe166d95a475ef21029d83"
        CHAOJIYING_KIND = 9102
        
        #超级鹰的第三接口类
        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()
        
        #简书验证类
        class CracToucClick():
            def __init__(self):
                self.url = "https://www.jianshu.com/sign_in"
                self.browser = webdriver.Chrome()
                self.wait = WebDriverWait(self.browser, 20)
                self.email = EMAIL
                self.password = PASSWORD
                self.chaojiying = Chaojiying_Client(
                    CHAOJIYING_USERNAME,
                    CHAOJIYING_PASSWORD,
                    CHAOJIYING_SOFT_ID
                )
        
            def __del__(self):
                self.browser.close()
        
            def open(self):
                '''
                打开网页输出用户名密码
                :return:None
                '''
                self.browser.get(self.url)#打开网页
                #定位账号输入点和密码输入点
                email = self.wait.until(EC.presence_of_element_located((By.ID, "session_email_or_mobile_number")))
                password = self.wait.until(EC.presence_of_element_located((By.ID, "session_password")))
                #填写内容
                email.send_keys(self.email)
                password.send_keys(self.password)
        
            def get_touclick_button(self):
                '''
                获取初始验证按钮,加载验证码图片
                :return:按键
                '''
                button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, "sign-in-button")))
                return button
        
            def get_touclick_element(self):
                '''
                获取验证图片对象
                :return:图片对象
                '''
                element = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, "geetest_widget")))
                return element
        
            def get_position(self):
                '''
                获取验证码位置
                :return: 位置的元组
                '''
                element = self.get_touclick_element()
                time.sleep(2)
                location = element.location
                size = element.size
                print(size,location)
                top,bottom,left,right = location['y']+100, 100+location['y']+size['height'], location['x'],location['x']+size["width"]
                top = int(top)
                bottom = int(bottom)
                left = int(left)
                right = int(right)
                return (top, bottom, left, right)
        
            def get_screenshot(self):
                '''
                网页截图
                :return:截图对象
                '''
                screenshot = self.browser.get_screenshot_as_png()
                screenshot = Image.open(BytesIO(screenshot))
                return screenshot
        
            def get_touclick_image(self, name="captch.png"):
                '''
                获取图片
                :param name:
                :return:图片对象
                '''
                top, bottom, left, right = self.get_position()
                print("验证码的位置",top, bottom, left, right)
                #截图对象
                screenshot = self.get_screenshot()
                captcha = screenshot.crop((top, bottom, left, right))
                captcha.save(name)
                return captcha
        
            def get_points(self,captcha_result):
                '''
                解析识别结果
                :param captcha_result:
                :return:
                '''
                groups = captcha_result.get("pic_str").split("|")
                locations = [[int(number) for number in group.split(",")] for group in groups]
                print("locations:", locations)
                return locations
        
            def touch_click_words(self, locations):
                '''
                点击
                :param locations:
                :return:
                '''
                for location in locations:
                    print(location)
                    ActionChains(self.browser).move_to_element_with_offset(
                        self.get_touclick_element(), location[0],
                        location[1]
                    ).click().perform()
                    time.sleep(1)
        
        
            def touch_click_verify(self):
                '''
                点击验证码
                :return:
                '''
                boutton = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, "geetest_commit")))
                boutton.click()
        
            def login(self):
                '''
                登录
                :return:
                '''
                submit = self.wait.until(EC.presence_of_element_located((By.ID, "_submit")))
                submit.click()
                time.sleep(10)
                print("登录成功")
        
            def crack(self):
                '''
                入口函数
                :return:
                '''
                self.open()#打开页面
                #获取登录按钮
                button = self.get_touclick_button()
                button.click()
                time.sleep(1)
                #开始获取验证码图片
                image = self.get_touclick_image()
                # bytes_array = BytesIO()
                # image.save(bytes_array, format="PNG")
                # #验证码识别
                # result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND)
                # print(result)
                # locations = self.get_points(result)
                # self.touch_click_words(locations)
                # self.touch_click_verify()
                # #判断是否登录成功
                # try:
                #     success = self.wait.until(
                #         EC.presence_of_element_located((By.CLASS_NAME, "btn write-btn"),"验证成功")
                #     )
                #     print(success)
                # except:
                #     #失败进行重新验证
                #     self.crack()
        
        if __name__ == '__main__':
            crack = CracToucClick()
            crack.crack()
        
        

模拟登录

  • 用户输入信息点击登录之后,浏览器就会向服务器发送一个登录请求,这个请求当中携带了用户的信息,服务器就会进行验证,验证之后会给客户端也就是浏览器一个凭证,有了凭证之后,客户端就可以去访问页面的的其他内容。

  • session和cookies

  • 在服务器中session记录了用户的状态。

  • cookies中只保存看了session ID的相关信息。服务器可以根据这个session id找到对应的session信息。这里面就有一个字段就记录了登录状态。

  • 当然在cookies中也肯就直接存放了一些凭证信息。

  • JWT

  • 近几年前后端分离越来越明显了,传统的前后端验证方式就是session和cookies.不是很适合前后端分离,后来就产生了JWT,(json web token),为了在网络运用传递和声明执行一种基于json的开放标准,实际上就是每次登录的时候同通过传递一个token字符串来记录登录状态。

  • JWT声明一般用来做身份提供者和服务提供者之间进行被认证的用户信息的传递,以便于客户端从服务器获取资源。也就是说,Token既可以被用来做身份验证,还可以进行信息的传递。

  • token是一个加密过的字符串。一般会以’.'号将toKen分成三段,
    分别是header,payload,signature

  • header声明了JWT的签名算法,RSA、SHA256。也可能会包含JWT的编号,数据类型整个信息的base64编码。

  • payload 通常用来存放一些业务需要的但是又不是很敏感的信息。

  • signature这就是一个签名,是把header和pyloadd的信息用密钥加密后形成的,是保存在服务端的。

  • 这3部分组合起来就是用户凭证了。

  • session和cookies的核心思想:就是你登录前后使用session和cookies要保持是一对儿。

  • jwt,就是你要以正确的方式请求是添加上这个jwt的信息就可以以登录状态访问。

JS逆向

  • 对于网页来说,执行逻辑都是依靠JS实现的,JS的代码是运行在客户端的,也就说js代码要在客户端加载运行,它也就是变成了透明的。因此导致JS的代码是不安全的,任何人都可以进行读、分析、复制、盗用和修改

  • 就需要JS的压缩、混淆、加密技术

  • 代码压缩:去除代码中不必要的空格、换行等内容,减低代码的可读性。还可以加代码的执行速度

  • 代码混淆:使用变量替换、字符串阵列化、控制流平坦化、多态变异、僵尸函数、调试保护等手段。就是让你找不到变量,就是让你看不懂。就是找不到逻辑。

  • 代码加密:可以通过一些手段对代码进行加密,转换成人无法理解阅读的代码。但是计算机可以执行的。抽象化加密,eval加密。。

  • 接口加密技术

    • 数据一般通过接口来进行获取的。网站或者App可以及那个你请求的数据集接口隐藏起来。
    • 完全开放的接口:就是没有任何防护措施
    • 接口参数加密:为了提升接口安全性,客户端和服务器约定一种接口检验方式,比如base64,hex,md5.aes des rsa等加密手段
  • js混淆:

    • 变量混淆:将含有意义的变量名、方法名、常量名随机变成无意义的乱码字符串,降低代码的可读性。
    • 字符串混淆:将字符串阵列化集中放置,并可进行md5或者base64编码加密存储,使得代码中不显示明文字符串
    • 属性加密:针对JS对象属性进行加密转化。隐藏代码中的调用关系。
    • 僵尸代码:无用代码,随机插入代码中。
    • 调试保护:不让代码进入debuger模式,你在js中加入一些js的调试语句,当别人想调试你的代码时,这个语句就会执行,调试模式就不能被正常开启或者使用时不能正常跟踪。
    • 多态变异:每次看代码,代码自身每次都不一样。
    • 锁定域名:你的js代码只能在固定的域名执行出正确的结果
    • 反格式化:如果你对网页的jS代码做格式操作,然后你的网页就死掉。
    • 特殊编码:自己定义规则
  • JS逆向分析思路

    • 分析URL构成以及请求方式和数据加载方式

    • 根据url中关键字搜索js代码文件

    • 根据url进行XHR断点调试

    • 寻找callstack中执行过的内容找到加密的字段

    • 将api/moviefan放到一个列表中

    • 列中加入时间戳

    • 将列表内容用逗号拼接

    • 将拼接好的结果用SHA1进行编码

    • 再将编码的结果和时间戳进行逗号拼接

    • 将拼接的结果以base64进行编码

  • 分析完之后转换为python

    • 这个过程就是在模拟人家的JS的实现逻辑
   import time
   import base64
   from typing import List,Any
   import requests
   import hashlib
 index_url = "https://dynamic6.scrape.cuiqingcai.com/api/movie?limit={limit}&offset={offset}&token={token}"
 limit = 10
 offset = 0

 def get_token(args:List[Any]):
     timestamp = str(int(time.time()))
     args.append(timestamp)
     sign = hashlib.sha1(",".join(args).encode("utf-8")).hexdigest()
     token_ = base64.b64encode(",".join([sign, timestamp]).encode("utf-8")).decode("utf-8")
     return token_

 args = ["/api/movie"]
 token = get_token(args=args)
 print(token)
 url = index_url.format(limit=limit,offset=offset,token=token)
 response = requests.get(url)
 print(response.json())

Pyppeteer

- 基于node.js开发的一个工具,有了它就可以通过js控制你的浏览器,也可以用在爬虫是,API相当完善。就是类似与selenium,selenium的功能它全有。它其实Puppeteer的python实现版本叫Pyppeteer,在Pyppeteer中它实际上类似与Chrome执行一些动作,Chorome再发布正式的版本之前会先在Chromium上测试执行。
- 安装
	 pip install -i https://pypi.douban.com/simple pyppeteer

超级厉害的爬虫框架——scrapy

  • 比如我们用requests.aiohttp去爬取数据的时候,异常处理。数据存储,任务调度等,从头写到尾你的的代码还不一定健全

  • 所以为了提高爬虫的编写效率就有了框架。

  • 简介:

    • scrapy是一个基于异步处理框架(Twisted)写的,scrapy框架是存python写的,架构清晰,灵活完整,能够完成各种需求。我们简单的制定几个模块就可以进行爬取了
    • 框架的架构:
      • Engine(引擎):用来处理整个系统的数据流处理、触发事件。
      • item(项目):定义了爬虫结果的数据结构,爬取的数据会被赋值成该对象
      • Scheduler(调度器):用来接收引擎发送过的请求并且将请求加入队列,并在引擎再次发起请求的时候提供给引擎
      • downloader(下载器):用于从网页下载内容
      • spiders(蜘蛛):定义了爬取的逻辑和网页的解析规则,主要负责解析响应并且生成提取的结果
      • item pipline(项目管道):负责处理从网页种抽取的项目,主要负责清洗、验证、存储数据
      • downlodar Middlewares(下载器中间件):位于引擎的和下载器之间的钩子框架,主要处理引擎和下载器之间的请求和响应
      • spider Middlewares(蜘蛛中间件)位于引擎和蜘蛛之间的钩子中间件,主要处理蜘蛛输入的响应和输出的结果以及新的请求。
  • 安装

    • pip install -i https://pypi.douban.com/simple scrapy
  • 项目结构

    • 创建命令scrapy startproject demo:demo为项目名称
    • scrapy.cfg:它是scrapy项目的配置文件,其中定义了项目配置文件路经、部署相关的内容。
    • iltems.py是定义数据结构的,所有的iltm的定义都可以放这里。
    • piplines.py是定义item pipliine的实现。
    • settings.py是全局的配置文件
    • middlewares.py是定义spider middlewares和downloader Middlerwares的实现
    • spiders:其中就是包含你的爬虫的url的指定和响应处理的地方
  • 基本运用:

    • 目标:创建项目、创建一个spider来抓取站点数据,然后进行处理,通过命令将抓的内容导出,将抓取的数据入库mongodb

    • 创建spider的方法:

      • 进入项目然后执行
      • scrapy genspider spidername 站点域名
    • 创建item

      • iltem是爬取的数据的容器,它的使用方法类似与字典,比起字典多了保护机制,可以避免拼写错误或者定义字段错误。
    • 解析response

      • 之前看到了parse方法的参数为response,也就是start_urls的里面的连接的爬取结果。我们就可以直接对response变量中包含的内容进行解析。或者进一步的找去连接爬取下一个请求
      • 分析网页进行解析
    • 加入iltem

      • 引入item的包进行使用
    • 后续的url的请求发起

      • 构造请求时利用scrapy.Request.,这里需要传递的必要的两个参数
        ——URL和callback
      • URL是请求的连接
      • callback:是回调函数,当指定的该回调函数之后,获取的响应将会传递到这个回调函数中。
    • 运行保存到文件中

      • scrapy crawl spidername -o name.json
    • 使用Item pipeline

      • 将结果保存到mongo中,或者进行筛选有用的item的就可以定义item pipLine来进行实现

      • 常用的操作

        • 清洗数据
        • 验证爬取的数据,检测爬取字段
        • 查重并丢弃重复的内容
        • 将爬取的结果保存到数据库中
      • 实现也很简单就是在item pipline中定义一个process_item方法。启用这个方法之后就可以调用,需要注意的是你必须在返回结果中包含数据的字典或者iltem对象,或者抛出异常DropIltem异常

      • process_item有两参数一个item每次产生的item都会作为参数传递过来,另一个就是spider它是Spider对象。

      • open_spider 必须包含一个参数spider

      • close_spider 必须包含一个参数spider

    • 在setting.py中开启item_pipline的功能

  • Spider

    • 最核心的类就是spider,它定义了如何爬取某个网站的流程和响应的解析。简单来说Spider的作用有两个1.定义爬虫的动作,2分析爬取的网页
    • 对于spider类来说整个的爬取循环过程
      • 以初始的url初始化Request,并设置回调函数。当该Request成功请求并返后是,将生产response,作为参数传递给回调函数
      • 在回调函数中分析返回的网页。1,解析后的字典类型数据和item对象
        。下一可以经过处理的或者直接可以的保存的数据的连接或者内容
      • 如果返回的是item对象,就可以金属数据的存储,如果设置了pipeline的话,会进行进一步的处理
      • 返回Request,南无Request执行成功后会继续的将response返回到回调函数
  • Spider类

    • name:scrapy就是通过这个字符串来初始话我们的spider,所以在项目中它保证唯一。你可以生成多个spider的实例
    • allowed_domains:允许爬取的域名,不在范围内的连接不会被跟进爬取。可以不指定,
    • start_urls:起始的url列表。当我们实现start_requets方法时默认会从这个列表开始抓取
    • custom_settings:这是一个字典,是专属于Spider的配置,此设置会覆盖全局的设置。而且此设置必须在初始化前更新,所以就必须定义为类变量
    • crawler:此属性是由from_crawler方法设置的,代表的是的spider类对应的Crawler对象,这个对象中有好多的组件,利用这个组件可以获取配置信息。
  • 除了以上基础属性还有一些常用方法:

    • start_requests:此方法用于生成初始请求,并且它返回的必须是一个可迭代对象。而且Spider的默认请求方法是GET,如果想要POST,需要重写此方法。或者使用scrapy.FormRequest()进行POST请求
    • parse:start_requests初次发起请求默认将响应返回的回调函数
    • cloese:当spider关闭时会被调用
  • Selector

    • 这个选择器时基于lxml构建的,支持xpth选择器和css选择器

    • 示例

      from scrapy import Selector
      
      body = """
      
      
      
          
          hello world
      
      
      
      
      
      """
      
      selector = Selector(text=body)
      title = selector.xpath("//title/text()")
      print(title)
      print(title.extract_first())
      
      
      
  • scrapy shell

    • 其实就是帮助你分析网页
    • scrapy shell url进入shell
    • 实质上就是向这个url发送一个请求
    • 就由响应response,你就可以利用响应进行分析
  • 功能强大到不行的中间件

    • Spider Middlewares

      • 这个是介入scrapy的spider处理机制的钩子架构。
      • 当downloader生成response之后,response就会被发送到Spider
        。在发送之前response就会先经过spider middlewares处理,当spider生成item时和Request之后还会经过spider Middlewares处理
      • 作用:
        • 处理downloader在给spider发送response之前,进行处理Response
        • 在Spider生成Request发送给调度器之前,进行处理。
        • Spider在item,发送给item pipeline前 进行处理
      • 使用说明:
        • 在scrapy中有定义好的SPIDER_MIDDLEWARES_BASE变量定义
        • 这个变量的内容如下:
          {
          ‘scrapy.spidermiddlewares.httperror.HttpErrorMiddlewares’:50,
          ‘scrapy.spidermiddlewares.offsite.OffsiteMiddlewares’:500,
          ‘scrapy.spidermiddlewares.referer.RerfererMiddlewares’:700,
          ‘scrapy.spidermiddlewares.urllength.UrlLengthMiddlewares’:800
          ‘scrapy.spidermiddlewares.depth.DepthMiddlewares’:900
          }
      • 核心方法
        • 内置的基础功能
          • process_spider_input(response, spider)
            • 当response通过Spider middlewares时方法被调用,处理response
            • 返回值None或者异常。如果返回了None,scrapy将继续处理response,调用其他的spider middlewares处理该respongse .如果抛出异常。scrapy将不会再调用任何的spider middlerWares直到spider自己处理response.
              也会调用Request的errback方法。这个方法将异常重定向到中间件,将异常信息输出,调用process_spider_output,这个时候继续出异常就会让process_spider_exception处理。
          • process_spider_output(response, result, spider)
            • result:包含了Request或Item对象的可迭代对象,也是就是spider返回值
            • 返回值必须包含Request或者Item对象的可迭代对象
          • process_spider_exception(response, exception, spider)
            • exception:就是异常对象
            • 返回值:None或者一条Request或者Item。如None就会继续处理这个异常,直到所有的SPider Middlewares被调用。如果返回Request或者Item则其他的SpiderMiddlewares的process_spider_output被调用,其他的process_spider_exception就不会调用。
          • precess_start_requests(start_requests, spider)
            • start_requests:包含了Requests的可迭代对象也就是Start Requests
            • spider 也就是spider对象,就是Start Requests所属的spider
            • 其必须返回一个包含Request对象的可迭代对象。
    • Downloader Middlewares

      • 它是请求和响应之间的中间件
      • 调度器出队列的请求发送下载器之前对其进行修改
      • 使用说明
        • 官方文档可查
      • 核心的方法:
        • process_requests(request, spider)

          • request:Request对像
          • spider:就是Request对像对应的spider对象
          • 调度器给下载器之前就会执行的方法。
          • 返回值必须是None或者Response对象、Request对象之一或异常。
        • process_response(requests, response, spider):

          • 下载器下载完之后,将响应给spider之前进行。
          • 返回Request对象,更低优先级的中间件执行继续放到调度器中执行,就会之再调用process_response。
          • 返回response就会调用更低级的中间件process_response,直到将所有的中间件调用完成。
          • 返回异常,会调用Request的errback方法进行回调,如果异常没有被处理最终会被系统忽略。
        • process_exception(reqeust,exception,spider)

          • 返回值None。中间件中更低级的process_exception继续执行。直到调用完毕
          • Response对象。中间件更低级process_exception就不会再执行就回去转去执行process_response。
          • Request对象。间件更低级process_exception就不会再执行就回去转去执行process_requests。
  • 如果遇到了Ajax页面

    • 简单回顾一下:
      • Downloder/Spider Middlewares得用法,process_requests,process_response,process_expection
      • Item Pipeline的用法,open_spider close_spider process_item
      • item.py中定义了要抓取的数据的字段。
      • spider_name:字符串是全局唯一,因为Spider这个库是根据字符串来初始化爬虫的。
      • 允许爬取域名。
      • GET请求方式是默认的Spider.Request
      • POST请求方式是Spider.FormRequest
  • 动态渲染的页面请求

    • 具体使用Gerapyppeteer结合scrapy
  • 分布式爬虫

    • 概念:
    • 数据结构:
      • 列表数据结构:
        lpush、lpop,rpush、rpop,可以用它实现队列的先进先出,也是先进出的栈。
        • 集合:数据的无序的且不重复,这样我么可以实现一个随机排序的不重复的爬取队列。
        • 有序集合:带有分数标识的,而scrapy的Request的方法有优先级控制。所以就可以用有序集合实现一个带有优先级队列的调度队列
  • 去重:

    • hhtp://wwqwd.com?page=1
    • scrapy内部方法request_fingerprint(),根据你发起的Request,携带的method,url,body,headers来根据sha1的方法进行计算。输出一串字符,如果有一点差别就会生产新的指纹,这个指纹就是scrapy过滤重复的请求的方法
  • 防止中断

    • scrapy 的request请求队列是在内存中的,只要爬虫一停这个队列就被释放了。想要做到停了还想从上次的request继续请求数据,我们在scrapy中指定一个爬虫队列的存储路径即可,scrapy crawl spider_name -s JOBDIR=crawls/spider
  • 实现

    • 共享一个爬虫队列
    • 去重,
    • 需要重写调度器
  • 原理

    • 将Request对象放到数据库中,但是数据库是存不了对象的,所有要将对象转换成字符串在存储,序列化,反序列化的操作利用pickle的库实现的,一般用push将Request对象存储到数据库中会调用_encode_request进行序列化,然后用pop取出数据,然后利用_decode_request方法进行反序列化。
  • 实例

    • pip install -i https://pypi.douban.com/simple scrapy-redis
    • 修改调度器:SCHEDULER = “scrapy_redis.scheduler.Scheduler”
    • 修改去重的方法:DUPEFILTER_CLASS = “scrapy_redis.dupefilter.RFPDupeFilter”
    • 连接redis:REDIS_URL = “redis://localhost:6379”
    • 列表持久化:SCHEDULER_PERSIST = True
  • scrapy部署

    • scrapyd,这是一个服务程序,它提供一些列的http的接口帮助我们部署、启动、停止、删除我们的爬虫。
    • 准备工作
      • 安装:pip install -i https://pypi.douban.com/simple scrapyd
      • 安装: pip install -i https://pypi.douban.com/simple scrapyd-client
      • 在python的script文件中找到scrapyd-deploy(没有后缀名文件),然后新建scrapyd-deploy.bat文件,文件内容。
      • 新建一个空的文件夹,在这个文件夹下执行scrapyd.
      • 进入项目工程,执行scrapyd-deploy。
      • 修改项目scrapy.cfg中的deploy为deploy:【项目id】
      • 打开scrapy.cfg中被注释的url = http://localhost:6800/。注意如果你的主机地址不是本机,需要更换ip端口号。
      • 在项目中的命令行执行,scrapyd-deploy 项目id。执行之后会在scrapyd的执行文件下有一个Eggs文件夹中包含当前项目于egg文件
        。注意。如过生成失败,不是项目错误的话,多执行几次直到生产eggs文件。
        -在项目中执行命令:curl http://localhost:6800/schedule.json -d project=myproject -d spider=somespider
      • 就可以在浏览器中看到具体运行状态
      • 具体的其他命令可以在官方文档https://scrapyd.readthedocs.io/en/stable/ 查看
  • 容器:
    docker,爬虫可直接做成容器进行运行。在构建容器前需要修改与外部有连接的地址信息。先导出爬虫使用到库的信息,然后进入容器,容器启动前进行安装。

  • scrapy对接kubernetes

    • 镜像的部署工具。可视化管理工具
  • 总结:从小白到高手必经之路爬虫

    • 初学者爬取不带反爬措施的,个人博客,爬取文章,一般使用requests也就够了。将页面源码请求下来,然后使用解析方法,PyQuery,lxml,Beatifulsoup,正则的方式将内容抠出。
    • 数据获取之后的存储。mongodb,mysql,kafka,redis
    • Ajax、动态渲染。这个时候用requests就不行了。可以做js逆向分析,找到这规律,利用python去模拟实现它的数据请求方式,来从服务器获取数据。还可以使用自动化工具:selenium、pyppeteer。使用自动化工具需要注意的是,人家服务器可能会识别webdriver。如果被抓到,就打不开目标站点,就需要自己去做隐藏。
    • 多进程、多线程、协程。爬虫是IO密集型任务,大部分时间都在等待网页的响应,如果网页响应速度慢,就要等到返回,但是这个空余的时间可以让CPU去干别的事情,比如去请求别的站点,解析已经下载的数据,将解析后的数据入库等等操作。对应的库threading multiprocessing.相比之下异步协程就更加的66了用aiohttp结合asyncio。(gevent、tornado)的工具。基本上想开多少并发就多少。用了他们速度也就上了,速度上来了,人家服务的反扒机制,就会肯将ip\账号、弹验证码、返回假数据.
    • 分布式:不管你是多进程、多线程、协程都是单机。如果要是做到规模化记得分布式搞定。重点在于任务队列共享,去重。redis、kafka,zookeeper。现在主流的就是scrapy结合redids
    • 验证码:大部分可以使用第三打码平台,先登录,获取cookies,然后带着cookies直接访问登陆后的页面。但是时效性。
    • 封id:使用代理。代理可以先用免费的去检测目标站点对代理的封杀程度。有30%使用率就不需要花钱找专门的高匿名代理。
    • 部署:容器,scrapyd部署
    • 法律:不要碰任何个人信息。查看清目标站点法律信息。爬取时注意目标服务器的运行情况,如果造成目标因访问量和数据下载宕机,可能会遭到法律追责。你的数据是否会流入市场。

APP爬虫

  • 数据有web端,也有移动端(手机上面的APP).
    APP的数据就比较依赖独立的服务器,比如请求一个登录接口来获取验证,其实类似与web中的ajax,手机端给服务器发起了请求,请求格式有很多种,json,XML,基本不会用HTML这种格式的。

  • 为了了解app在运行过程中如何发起请求,然后拿到数据的。就会使用抓包工具。Fiddler,Charles,mitmproxy,anyproxy。

  • 使用了工具还有可能拿不到数据。就是APP本身设置了不走代理。APP设置了SSL.还有带了加密参数。为了破解加密你就要做逆向分析。APP为了防止逆向还做了逆向加固,就是成为一个壳。将核心代码进行编译形成一个so库,因此就是要对so库进行逆向才能够了解器逻辑。服务器还做风控处理。

  • 抓包

    • 安卓7.0以下的抓起来很容易。在PC运行你的抓包工具,开启一个HTTP代理服务,然后手机和PC在同一局域网内,让手机的代理设置成的PC的ip。进行抓包。
    • 抓不到咋办:强制全局代理,就是他的底层还是走的TCP协议,就可以使用TCP的数据包重定向。https://gtihub.com/madeye/proxydroid
  • 手机代理:

    • 如果不通过PC上的软件抓包,还可以直接在手机上设置抓包软件,这是通过VPN的方式将网络包转发给本地的手机的代理服务器,代理服务器再将数据发个服务端。HttpCanary PacketCapture Netkeeper
  • 配置方法:
    工具需要修改自己的手机配置,具体方法都和手机的相关

  • 脱壳:
    脱壳工具:FRIDA-DEXDump,Dumpdex

  • 反汇编:

    • 一些apk里面加密的可能是直接写入so格式的动态链接库。IDA https://www.hex-rays.com/
  • 模拟:

    • 因为风控和app逆向就需要我们模拟手机操做
  • adb

    • 实现一些手机自动化的操纵,但是功能有限
  • 触动精灵,按键精灵

  • Appium

    • 类似于selenium。APPium是手机的一款自动化测试工具
  • AirTest

    • 手机自动化工具,网易开发的比APPium方便
  • Appium/Airtest + mitmdump

    • 自动化加抓包
  • mitproxy:

    • 安装证书:手机和PC都需要安装
    • PC和手机要运行在同一局域网
    • 然后你在PC运行mitmdump
    • 查看PC的IP
    • 在手机上设置代理,IP就是你的PC端口就是mitmdump运行起来的端口
    • 运行 mitmproxy就能在控制台看到手机发起任何请求

你可能感兴趣的:(python爬虫入门学习,轻量级爬虫,环境搭建,python,爬虫,强化学习)