使用requests做爬虫

文章目录

  • 爬虫基础
  • requests
  • Python File(文件) 方法
        • open()
        • file 对象
  • Python 正则表达式
  • 数据解析
  • 验证码登录
  • IP代理
  • 异步爬虫
  • random
  • 笔记

爬虫基础

  1. 爬虫:模拟浏览器上网,抓取数据。

  2. www.baidu.com/robots.txt

  3. 常用请求头

    • User-Agent
    • Connection:请求完毕后,是断开还是保持连接
    • cookie:
  4. 常用响应头

    • Content-Type:服务器响应回客户端的数据类型
  5. https

    • 安全的超文本传输协议
    • 加密方式:
      • 对称密钥加密
      • 非对称密钥加密
      • 证书密钥加密

requests

  1. 使用:

    • 确定url
    • 发送请求
    • 获取响应数据
    • 存储
  2. get方法

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

    params为url参数

    headers:UA伪装

  3. post方法

    requests.post(url= , data= headers= )

    data类似params

  4. json

    • json.dumps()函数是将字典转化为字符串

    • json.dump()函数的使用,将json信息写进文件

    • json.loads()函数是将字符串转化为字典

      with open('BVid.json', 'w', encoding='utf-8') as fp:
          json.dump(dict_BV,fp=fp,ensure_ascii=False)
          #因为json.dumps 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定		#ensure_ascii=False
      
    • json.load()函数的使用,将读取json信息

      with open('1.json', 'r') as f:
        data = json.load(f)
      
    • 客户端要向服务器端发送一个json字符串,服务器端要接收并处理。下面演示正确的代码:

      import json
      with open('1.json', 'r') as f:
          data = json.load(f)
      data = {"company_data": json.dumps(data)}
       
      # urlopen()的data参数默认为None,当data参数不为空的时候,urlopen()提交方式为Post
      from urllib import request, parse
      url = r'http://192.168.165.4:8000/show/report/'
      company_data = parse.urlencode(data).encode('utf-8')
      req = request.Request(url, data=company_data)
      print(company_data)
      page = request.urlopen(req).read()
      page = page.decode('utf-8')
       
      print('page是什么', page, type(page))
      
    • 接收的话,下面的代码只是接收并打印,并没有进行一系列的判断然后去入库之类的,因为我还没做:

      def receive_data(request):
          if request.method == 'POST':
          print(request.get_full_path())
          print(request.body)
          data = request.POST.get("company_data")
          if data:
              try:
                  data = json.loads(data)
                  print("企业数据", type(data), data)
                  return HttpResponse(data)
              except ValueError as e:
                  print(str(e))
          else:
              return HttpResponse("no data")
      
  5. requests.session()

    requests库的session会话对象可以跨请求保持某些参数,说白了,就是比如你使用session成功的登录了某个网站,则在再次使用该session对象求求该网站的其他网页都会默认使用该session之前使用的cookie等参数

    • 例:

      #requests.session():维持会话,可以让我们在跨请求时保存某些参数
      import requests
      #实例化session
      session = requests.session()
      #目标url
      url = 'https://www.douban.com/accounts/login'
      form_data = {
          'source': 'index_nav',
          'form_email': 'xxx',
          'form_password': 'xxx',
          'captcha-solution': 'stamp',
          'captcha-id': 'b3dssX515MsmNaklBX8uh5Ab:en'
      }
      #设置请求头
      req_header = {
          'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
      }
      #使用session发起请求
      response = session.post(url,headers=req_header,data=form_data)
      if response.status_code == 200:
       
          #访问个人主页:
          url = 'https://www.douban.com/people/175417123/'
       
      	response = session.get(url,headers = req_header)
       
          if response.status_code == 200:
       
              with open('douban3.html','w') as file:
       
                  file.write(response.text)
      

  1. 手动设置cookie

    • cookie格式化(手动)

      # 处理cookie内容为字典
      cookie = "SINAGLOBAL=821034395211.0111.1522571861723; wb_cmtLike_1850586643=1; [email protected]; wb_timefeed_1850586643=1; "
      cookie_dict = {i.split("=")[0]: i.split("=")[1] for i in cookie.split("; ")}
      cookie_dict = {i.split("=")[0]:i.split("=")[-1] for i in cookie.split("; ")}
      response =  seesion.get(url=url, headers=header, cookies=cookie_dict)
      
    • try:
          res = requests.post(url=login_url, headers=headers, data=body)
          cookies = res.cookies
          cookie = requests.utils.dict_from_cookiejar(cookies)
          return cookie
      except Exception as err:
          print('获取cookie失败:\n{0}'.format(err))
      
    • requests.utils.dict_from_cookiejar(cj)
      requests.utils.cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True)
      requests.utils.add_dict_to_cookiejar(cj, cookie_dict)
      
  2. 批量添加引号:

    (\S*).\S)

    ‘$1’:‘$2’,

    (\S*): ([0-9a-zA-Z]*)

    ‘$1’:‘$2’,

    表示整个正则表达式匹配到的内容,表示捕获组匹配到的内容(其中,n>=1)

  3. response = requests(url)
    
    #改变编码
    response.encoding = 'utf-8'
    #通用处理中文乱码的解决方刻
    img_name = img_name.encode(iso-8859-1).decode('gbk')
    #返回字符串
    response.text
    #返回对象
    response.json()
    #二进制
    response.content
    

Python File(文件) 方法

  1. open()
    open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
    

    参数说明:

    • file: 必需,文件路径(相对或者绝对路径)。
    • mode: 可选,文件打开模式
    • buffering: 设置缓冲
    • encoding: 一般使用utf8
    • errors: 报错级别
    • newline: 区分换行符
    • closefd: 传入的file参数类型
    • opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。

    mode 参数有:

    t 文本模式 (默认)。
    x 写模式,新建一个文件,如果该文件已存在则会报错。
    b 二进制模式。
    + 打开一个文件进行更新(可读可写)。
    U 通用换行模式(不推荐)。
    r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
    rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
    r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
    rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
    w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
    wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
    w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
    wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
    a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
    ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
    a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
    ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
  2. file 对象
    序号 方法及描述
    1 file.close()关闭文件。关闭后文件不能再进行读写操作。
    2 file.flush()刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入。
    3 file.fileno()返回一个整型的文件描述符(file descriptor FD 整型), 可以用在如os模块的read方法等一些底层操作上。
    4 file.isatty()如果文件连接到一个终端设备返回 True,否则返回 False。
    5 file.next()返回文件下一行。
    6 [file.read(size])从文件读取指定的字节数,如果未给定或为负则读取所有。
    7 [file.readline(size])读取整行,包括 “\n” 字符。
    8 [file.readlines(sizeint])读取所有行并返回列表,若给定sizeint>0,则是设置一次读多少字节,这是为了减轻读取压力。
    9 [file.seek(offset, whence])设置文件当前位置
    10 file.tell()返回文件当前位置。
    11 [file.truncate(size])截取文件,截取的字节通过size指定,默认为当前文件位置。
    12 file.write(str)将字符串写入文件,返回的是写入的字符长度。
    13 file.writelines(sequence)向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。

Python 正则表达式

re 模块使 Python 语言拥有全部的正则表达式功能

  1. 正则表达式模式

    模式 描述
    ^ 匹配字符串的开头
    $ 匹配字符串的末尾。
    . 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
    […] 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’
    [^…] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
    re* 匹配0个或多个的表达式。
    re+ 匹配1个或多个的表达式。
    re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
    re{ n} 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 “Bob” 中的 “o”,但是能匹配 “food” 中的两个 o。
    re{ n,} 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。“o{1,}” 等价于 “o+”。“o{0,}” 则等价于 “o*”。
    re{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    a| b 匹配a或b
    (re) 对正则表达式分组并记住匹配的文本
    (?imx) 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
    (?-imx) 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
    (?: re) 类似 (…), 但是不表示一个组
    (?imx: re) 在括号中使用i, m, 或 x 可选标志
    (?-imx: re) 在括号中不使用i, m, 或 x 可选标志
    (?#…) 注释.
    (?= re) 前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
    (?! re) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
    (?> re) 匹配的独立模式,省去回溯。
    \w 匹配字母数字及下划线
    \W 匹配非字母数字及下划线
    \s 匹配任意空白字符,等价于 [ \t\n\r\f]
    \S 匹配任意非空字符
    \d 匹配任意数字,等价于 [0-9].
    \D 匹配任意非数字
    \A 匹配字符串开始
    \Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
    \z 匹配字符串结束
    \G 匹配最后匹配完成的位置。
    \b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
    \B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    \n, \t, 等. 匹配一个换行符。匹配一个制表符。等
    \1…\9 匹配第n个分组的内容。
    \10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。
    [0-9a-fA-F] 可以匹配数字,大小写形式的a~f
  2. findall
    在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。

    注意: match 和 search 是匹配一次 findall 匹配所有。

    语法格式为:

    findall(pattern, string, flags=0)
    

    参数:

    • string : 待匹配的字符串。
    • pattern:为正则表达式
    • flag为替换规则,如是否区分大小写。
    • 若匹配到一个及以上的结果返回一个list。
  3. re.subn与re.sub

    函数原型:subn(pattern, repl, string, count=0, flags=0)

     			 sub(pattern, repl, string, count=0, flags=0)
    

    参数:

    • pattern为正则表达式,
    • repl为替换的文本,
    • string是被匹配的文本,
    • count是替换次数,缺省为0,表示全部替换,
    • flag是匹配规则,如是否区别大小写等,可省略。

    subn返回的结果是一个元组(替换后的字符串,替换次数)。

    sub仅返回替换后的字符串。

  4. re.match函数
    re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。

    函数语法

    re.match(pattern, string, flags=0)
    

    函数参数说明:

    参数 描述
    pattern 匹配的正则表达式
    string 要匹配的字符串。
    flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志

数据解析

正则、bs4、xpath

  • 数据解析原理:
    • 定位标签
    • 提前标签、标签属性中存储的数据值
  1. bs4数据解析的原理:
    • 实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
    • 通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
    • 提供的用于数据解析的方法和属性:
      • soup.tagName:返回的是文档中第一次出现的tagName对应的标签
      • soup.find():
      • find(‘tagName’):等同于soup.div
      • 属性定位:
        soup.find(‘div’,class_/id/attr=‘song’)
        soup.find_all(‘tagName’):返回符合要求的所有标签(列表)
      • select:
      • select(某种选择器(id,class,标签…选择器)),返回的是一个列表。
        层级选择器:
        soup.select(‘.tang>ul>1i>al’):>表示的是一个层级
        soup.select(‘.tang>ul a’):空格表示的多个层级
      • 获取标签之间的文本数据:
        soup.a.text/string/get_text()
        text/get_text():可以获取某一个标签中所有的文本内容
        string:只可以获取该标签下面直系的文本内容
        获取标签中属性值:
      • soup.a[‘href’]
  2. xpath:
    • 实例化一个etree对象:

      • etree.parse(fileName)
      • etree.HTML(page_text)
      • etree.tostring()方法即可输出修正后的HTML代码,但是结果是bytes类型
    • 定位

      • 标签/ 或 // 或 ./ 或 …/
      • 属性 [@attribute]
      • 取文本 /text()
      • alltext = data.xpath(‘string(.)’)
      • 取属性 /@attr
  • 谓语:

    • [1]
    • [last()]
    • [last()-1]
    • [position()❤️] 前面的两个
    • [@lang=‘eng’]
    • [price>35.00]
  • 选取未知节点

    • *匹配任何元素节点。
      /*/*/*/BBB 选择所有的有3个祖先元素的BBB元素
    • @*匹配任何属性节点
    • node()匹配任何类型的节点
  • XPath 运算符

    • 运算符 描述 实例 返回值
      | 计算两个节点集 //book | //cd 返回所有拥有 book 和 cd 元素的节点集
      + 加法 6 + 4 10
      - 减法 6 - 4 2
      * 乘法 6 * 4 24
      div 除法 8 div 4 2
      = 等于 price=9.80 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。
      != 不等于 price!=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
      < 小于 price<9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
      <= 小于或等于 price<=9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
      > 大于 price>9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
      >= 大于或等于 price>=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
      or price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
      and price>9.00 and price<9.90 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
      mod 计算除法的余数 5 mod 2 1

验证码登录

  1. 获取验证码图片
  2. 识别验证码
  3. 获取登录url(post方法、勾选preserve log)
  4. 登录,查看response.status_code

IP代理

  1. import requests
    proxies = {
      "http": "10.10.1.10:3128", #http只能访问http类型
      "https": "10.10.1.10:1080",#https只能访问https类型
    }
    
    requests.get("http://example.org", proxies=proxies)
    
  2. 若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 语法:

    #私密代理指定的是付费的代理,需要用户名和密码
    proxies = {
        "http": "user:[email protected]:3128/",
    }
    
  3. 要为某个特定的连接方式或者主机设置代理,使用 scheme://hostname 作为 key, 它会针对指定的主机和连接方式进行匹配。

    proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
    

异步爬虫

  1. 多线程(threading 模块):

    • https://blog.csdn.net/wqx521/article/details/82754617
      https://www.runoob.com/python3/python3-multithreading.html
      
    • 模板:

      import threading
      import time
      
      class myThread (threading.Thread):
          def __init__(self, threadID, name, counter):
              threading.Thread.__init__(self)
              self.threadID = threadID
              self.name = name
              self.counter = counter
          def run(self):
              print ("开启线程: " + self.name)
              # 获取锁,用于线程同步
              threadLock.acquire()
              print_time(self.name, self.counter, 3)
              # 释放锁,开启下一个线程
              threadLock.release()
      
      def print_time(threadName, delay, counter):
          while counter:
              time.sleep(delay)
              print ("%s: %s" % (threadName, time.ctime(time.time())))
              counter -= 1
      
      threadLock = threading.Lock()
      threads = []
      
      # 创建新线程
      thread1 = myThread(1, "Thread-1", 1)
      thread2 = myThread(2, "Thread-2", 2)
      
      # 开启新线程
      thread1.start()
      thread2.start()
      
      # 添加线程到线程列表
      threads.append(thread1)
      threads.append(thread2)
      
      # 等待所有线程完成
      for t in threads:
          t.join()
          #Join的作用是阻塞进程直到线程执行完毕。通用的做法是我们启动一批线程,最后join这些线程结束
          #此处join的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的join函数
          #通过传给join一个参数来设置超时,也就是超过指定时间join就不在阻塞进程。
      print ("退出主线程")
      
  2. 多进程(multiprocessing模块):

    • multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件

    • https://blog.csdn.net/ctwy291314/article/details/89358144
      
    • 模板:

      import multiprocessing as mul
      
      
      def f(x):
          return x ** 2
      
      
      if __name__ == '__main__':
          pool = mul.Pool(5)
          rel = pool.map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
          pool.close()#进程池不再创建新的进程
          pool.join() #wait进程池中的全部进程。必须对Pool先调用close()方法才能join
          print(rel)
      
      

random

import random

print( random.randint(1,10) )        # 产生 1 到 10 的一个整数型随机数  
print( random.random() )             # 产生 0 到 1 之间的随机浮点数
print( random.uniform(1.1,5.4) )     # 产生  1.1 到 5.4 之间的随机浮点数,区间可以不是整数
print( random.choice('tomorrow') )   # 从序列中随机选取一个元素
print( random.randrange(1,100,2) )   # 生成从1到100的间隔为2的随机整数

print random.choice('abcdefghijklmnopqrstuvwxyz!@#$%^&*()')# 随机字符:
print random.sample('zyxwvutsrqponmlkjihgfedcba',5)     # 多个字符中生成指定数量的随机字符:

# 从a-zA-Z0-9生成指定数量的随机字符:
ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 8))
print ran_str

# 多个字符中选取指定数量的字符组成新字符串:
print(''.join(random.sample(['z','y','x','w','v','u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a'], 5))) 

# 随机选取字符串:
print random.choice(['剪刀', '石头', '布'])

# 打乱排序
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print random.shuffle(items)

a=[1,3,5,6,7]                # 将序列a中的元素顺序打乱
random.shuffle(a)
print(a)

笔记

  • 动态加载,ajax请求,局部刷新,XHR

  • __name__:

    1. 如果模块是被导入,__name__的值为模块名字
    2. 如果模块是被直接执行,__name__的值为’__main__’
    

你可能感兴趣的:(Python,爬虫)