python 爬虫 闲话 基础入门

说在前面的话

  • python在自身历史版本中主要是python2和python3。我们不去表述各种的python实现,用C(CPython)\C++(Pyston)\Java(Jython)\C#(IronPython)\Ruby\JS都有, 我们只去说一下基于python3的最基础的使用和应用。当然很多的教程是以python2来写的,如果我们遇到其实并不影响我们去学习。
  • 浏览器主要是Chrome

一钱思路

  1. 拿到http://**.com/path/to/page 的地址, 将地址在浏览器(Chrome)中打开
  2. 在浏览器加载完地址且能看到你想看到的页面后,按F12 或者 直接在页面 右键=> 点击检查 ,页面会在某一个侧(默认是页面的右侧或者下侧)弹出如下图的 开发者工具, 图中的标红的箭头、 Elements、Network很关键,几乎是囊括了web爬虫所需的所有前置条件。
Chrome开发者工具
  1. 一般是箭头和Elements组合来使用,使用的目的是什么?就是确定你要爬取的数据在页面的什么地方。 步骤是:先点击Elements,它的紧下侧会有强调横线出现,同时整体下方出现的是html源码页面,看到<!DOCTYPE html> 就是了; 然后点击最左侧的箭头,箭头的颜色会变成蓝色或者颜色加重,随后把鼠标慢慢挪到真正的页面内容上,就会发现不一样了,这个时候html 源码页面的光标会随着你的鼠标在实际的可见页面移动而移动。而且点击一下实际可见页面的随意内容,html源码的光标就会立刻停在源码的位置, 实际页面不会发生变化。
  2. Elements + 箭头的 页面定位操作, 每一次定位 箭头都要从新点击一次。
  3. 对于Network点击后看到的是页面加载时的所有页面接口,如果说Elements + 箭头对应的静态页面内容的话,Network对应的就是动态加载的数据;明确哪些数据是有哪个接口提供的以及这个接口需要什么参数、cookie等关键性的请求依据都是从Network获取的
  4. 如果在浏览器中输入的地址没有得到具体的页面,而是一个登陆页面,此时就需要箭头+Elements+Network一起上了, 先用箭头+Elements明确登陆页面的需要输入的元素在哪里,在葱Network中确定登陆接口;如果登陆需要验证码还要从第三方的接口或者验证码的破解来辅助登陆。
  5. 以上都是基于浏览器可以直接看到数据的情况,如果页面是纯粹的动态加载,那么就要涉及到对页面js的逆向工程及更深层次的技术,此处不做深入谈论; 而如何确定页面是不是动态加载的呢?这个可以在Elements中找到答案, 去Elements的html页面查询真实页面的数据项,如果查不到基本上可以确定数据项是动态加载的, 这个时候需要去Network中找跟我们地址url对应到接口来确定页面数据是由哪个接口来动态提供的。如果只是简单需求可以用Selenium解决这个问题

二两概念

  1. request 和 response是什么?

request :可以通俗理解成对页面发起网络请求response: 可以通俗理解成对网页请求后,响应返回的数据request和response在很多类或者方法定义的主要关键字,又或者是约定成俗的写法; 在爬虫任务进行时的信息数据的两个不同的流向代表。

  1. 能抓取怎样的数据?

网页文本、图片、视频、二进制流

  1. 解析方式有哪些?

直接处理、json解析、正则表达式、beautifulsoup、pyquery 、 Xpath

  1. 怎样解决js渲染的问题?

分析Ajax请求、用selenium / webdriver、Splash、Pyv8

三分技术

  1. requests (请求页面)
  1. xpath (解析页面)
  • xpath是一门在 XML 文档中查找信息的语言,说的通俗点就是我们爬取的网页是一种有着基本款式和样式的本文信息, xpath就可以解析这些信息,从而取到我们想要的内容。
  • xpath是一种技术,在python中常用的库是 lxml
  • 为啥要用xpath先在页面搞一下? 因为我们不知道页面是什么样的,那么爬取到数据后我们也没有办法知道取哪里的信息为我所用,所有要运行所有的爬取程序之前先用浏览器目测一下具体哪些内容可以怎么接下下来。
  • xpath入门连接: https://www.runoob.com/xpath/xpath-tutorial.html
  • 实际使用中, 以chrome浏览器为例,chrome浏览器是有自己的插件的,这个插件叫做XPath Helper, 它的作用是直接从页面可以快速的获取页面中我们想要的内容的xpath路径。如果是其它浏览器的基本操作前边也都是一样的,只是需要人工来找到想要内容的xpath路径,这样就需要爬虫代码的编写者非常熟悉xpath语法了。
  • 无论是否是chrome浏览器,在看一个页面的元素xpath路径时都是有一个基本的步骤的
1、用浏览器(IE 和 chrome为例)打开想要爬取的页面地址
2、页面上任何位置 右键 --> 检查 (检查元素)  或者直接按 F12 (不同浏览器说辞可能不一样,但是含义是相同的),会看到浏览器的下方或者右侧弹出如下两张图的界面,最左侧的箭头+方块的图标选中后就可以在页面选择元素了,也可以直接在弹出的界面中的页面源码中选择元素
import requests
from lxml import etree
if __name__ == __main__:
    headers = {
        User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
    }
    url = https://anqiu.58.com/ershoufang/
    page_text = requests.get(url = url,headers = headers).text
    tree = etree.HTML(page_text)
    li_List = tree.xpath(//section【@class = list】/div)
    fp = open(58.txt,w,encoding=utf-8)
    for li in li_List:
        title = li.xpath(./a/div【2】//div/h3/text())【0】
        print(title)
        fp.write(title+\n)
  1. 正则表达式 (解析页面)
    1. 通过网页请求requests返回的数据取得text文本内容
    2. 通过python的re 正则库,可以对文本内容进行正则匹配从而解析到想要的数据内容
  1. BeautifulSoup4 (解析页面)
  • BeautifulSoup也是python的一个库,类似于xpath同样是对页面进行解析,实现原理有所不同,它是基于DOM的,解析后可以按照DOM树对页面进行提取数据,懂得css等前端技术的人用起来会更顺手,且相对于xpath要API非常人性化,支持css选择器
  • 灵活性和性能来说bs4没有xpath好用
  • 文档传送门: https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
  1. Selenium: 它本身是Web应用程序测试的工具,直接在浏览器中运行, 所以它可以做到像浏览器一样把数据全部都加载出来,这样一来哪怕是动态加载也是可以拿到加载后的数据的。它本身是专门针对测试的工具 ,所有还是测重测试方向,官网传送门: https://www.selenium.dev/;而python调用selenium的API文档可以在这里看到 https://selenium-python.readthedocs.io/ 网站中有详细的说明以及对Drivers的下载链接地址。
  2. Scrapy 分布式爬虫框架,

主要步骤: 创建工程、爬虫文件、执行

  • scrapy startproject xxxPro
  • 在项目目录下输入: scrapy genspider spiderName www.XXX.com 创建爬虫文件
  • 框架会自动创建所有需要的items pipline settings.py 中间件等默认文件,而开发者只需要去根据需求去修改对应的文件来完成整个爬虫任务的构建即可
  • 在项目目录下执行: scrapy crawl spiderName

bitmap 基本原理介绍及基本应用 python 代码实现

一、 bitmap 是什么东东

简单且抽象来说bitmap用一个bit位来标记某个元素所对应的value,而key就是该元素。而它主要用来解决海量数据中元素查询,去重、以及排序等问题。

二、基本概念梳理

#  存储单位
#  换算的方式GB=>MB=>KB=>Byte=>Bit
#  1、 1 G = 1024 M ; 1M = 1024 KB; 1 KB = 1024 Bytes ; 1 Byte = 8 bit
#  2、 1 G = 1024 M = 1024 * 1024 KB = 1024 * 1024 * 1024 Byte = 1024 * 1024 * 1024 * 8 bit
#  3、 1024 = 2^10 --> 1GB = 2^30 bit

一个整数型的数字在一台31位的计算机中占4个字节,也就是32个bit;而bitmap算法就是用对应的32个bit位来表示十进制的0-31个数。

比如这么一个场景:有一台内存为 2 GB 的 PC,其硬盘中的一个存储了 30 亿个无符号整型数据文件(数据无重复),我们要对其进行排序。

我们来简单估算一下,这个数据文件的大小为 2 * 3 * 10^9 /2^30 约为 5.6 GB,,而我们的计算机就只有2G的内容,显然将这个数据文件直接读入内存是办不到的。我们来看一下如果用bitmap的思想,假设能用一个 bit 位来标示一个 int 整数,30亿个整数需要的内存空间为 3 * 10^9/8/2^20 大概为 357.6 MB,这样需要的存储空间将大大减少,我们可以轻易将这 30 亿个 int 数放到内存中进行排序了。

具体的做法就是,申请一个 int 长度为 N//32 + 1 的数组。N 表示要进行查找的最大整数,我们可以遍历一轮数据获得,通过数组中的每个元素在内存在占 32 位对应表示十进制数 0-31,如何在用bit表示这个具体的数字就是将二进制中对应该自然数字的位置的值设置成1即可。

array[0] 可表示 0-31
array[1] 可表示 32-63
array[2] 可表示 64-95

思想相对比较简单,关键是十进制和二进制bit位需要一个 map 映射表,把10进制映射到bit位上。

(1)如何确定十进制的数值在数组的那个位置?也就是数组的下标该怎么计算。基本公式是 loc_index = (int_value <= N / 32) ,loc_index即为n对应的数组下标,什么意思呢?就是用数字去除一个32,然后取不大于结果的最大整数。例如n = 59, 则 59 / 32 = 1.84375,而不大于1.84的最大整数是1 ,因此59在a[1]中。

(2)接下来是确定一个数字在对应数组内的bit位置。利用的是求模公式;同样的数字59 bit_loc = N % 32 ,例如 n = 59, bit_loc = 59 % 32 = 27

三、python 知识回顾

python世界中10进制转换成其他进制的内置函数
# 1、十进制转二进制:bin(10) --> 输出:'0b1010' 前缀:是字符串类型 0b:表示2进制
# 2、十进制转八进制:oct(10) --> 输出:'0o12'   前缀:是字符串类型 0o:表示8进制
# 3、十进制转十六进制:hex(10) --> 输出:'0xa'  前缀:是字符串类型 0x:表示16进制
#    可以通过format函数把输出的前缀去掉,例如 format(64, 'b') --> 1000000 或者 '{:b}'.format(64) --> 1000000
#   '{:o}'.format(10) --> 12 ;'{:x}'.format(10) -->a
注意: 到bin函数返回二进制数字表示的形式是采用了负号,而不是补码的形式。通过 bin(-27 & 0b1111111111111111)
<< 运算数的各二进位全部左移若干位; >> 运算数的各二进位全部右移若干位
# print(64 << 2) --> 256
# 移动过程逻辑上解释:
#   我们通过函数 format(64, 'b') --> 1000000;其实二进制 1000000 相当于 1000000.0, 小数点位在末尾直接省略了形式;
#   我们上学时学习的时候听到过这样一种描述:数字的左移相当于小数点位的右移,所以 1000000.0 小数点位右移两位变成 100000000.0
#   再次通过二进制转换十进制 int('0b100000000', 2) --> 256
#   print(64 >> 2) --> 16
#   同样的道理 1000000.0 的数字右移相当于小数点位左移, 1000000.0 左移小数点变成了 10000.00; 我们不用函数直接用数学的逻辑来做转换
#   1 * 2^4 + 0 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0  = 16
取模, 除法
#  1、二进制的基础上,与n取模其实就是和n-1相与 ;a%(2^n) 等价于 a&(2^n-1),而&操作比%操作具有更高的效率
#  2、“ / ” 为浮点数除法,返回浮点结果 ; “ // ” 表示整数除法,返回不大于结果的一个最大整数
#    print(10 / 3) --> 3.333333; print(10 // 3) --> 3
class BitMap2(object):
    def __init__(self):
        self.n = 5
        self.bitsize = 1 << self.n
        self.typecode = 'I'  
        self.lowerbound = 0  # 若数组中有负数,则所有数都减去最小的那个负数

    def load(self, inp):
        mini = min(inp)
        if mini < 0:
            self.lowerbound = -mini  # 如果数组中有<0的数,则所有数都要减去最小的那个负数
            inp = [i + self.lowerbound for i in inp]
        maxi = max(inp)
        # 确定数字在数组中的位置
        num_arr = math.floor(maxi / self.bitsize) + 1
        self.arr = array(self.typecode, [0] * num_arr)
        for x in inp:
            self._set(x)

    def _set(self, x):
        '''
        将x在数组中对应元置为1
        '''
        arr_idx = x >> self.n  # 元素在第几个数组中,等价于x // 2**self.n
        bit_idx = x & (self.bitsize - 1)  # 元素在相应数组中的第几个bit位,等价于x % 2**self.n
        self.arr[arr_idx] |= 1 << bit_idx  # 在这个数组里的不断的累加 2**n 最终的结果其实就是二进制转换成十进制的值

    def search(self, x):
        if self.lowerbound != 0:
            x += self.lowerbound
        arr_idx = x >> self.n
        bit_idx = x & (self.bitsize - 1)
        # 这里有问题,正常情况需要做数组长度的前置验证逻辑
        existence = True if self.arr[arr_idx] & (1 << bit_idx) else False
        return existence

    def sort(self):
        sorted_seq = []
        for arr_idx, a in enumerate(self.arr):
            for bit_idx in range(self.bitsize):
                if a & (1 << bit_idx):
                    sorted_seq.append(arr_idx * self.bitsize + bit_idx - self.lowerbound)
        return sorted_seq

if __name__ == '__main__':
    bitMap = BitMap()
    bitMap.load([3, 2, 4])
    print(bitMap.search(3))

numpy array create 创建数组 开始步入 numpy数组的世界

numpy array create: 学习numpy的创建数组是对这个python工具包学习必不可少的入门步骤,之前–a few years ago的文章(numpy 数组属性 区别于 python list 清仓大甩卖)中我们已经对numpy array的属性做了入门级的介绍,本篇我们又要对create numpy array做入门级的介绍了。

我们会用到的函数方法做入门级的介绍,同时会做一些基础级的注意事项说明。

1. 基础创建函数

numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)
名称描述
object数组或嵌套的数列
dtype数组元素的数据类型,可选
copy对象是否需要复制,可选
order创建数组的样式,C为行方向,F为列方向,A为任意方向(默认)
subok默认返回一个与基类类型一致的数组
ndmin指定生成数组的最小维度
array 参数说明
    # 1、基本创建方式
    a = np.array([3, 4, 5])
    print('a=', a)
    # 2、显式指定纬度
    b = np.array([3, 4, 5], ndmin=1)
    print('b=', b)
    c = np.array([3, 4, 5], ndmin=4)
    print('c=', c)
    # 3、隐式多维度
    d = np.array([[3, 4, 5], [6, 7, 8]])
    print('d=', d)
    # 4、显式多维度
    e = np.array([[3, 4, 5], [6, 7, 8]], ndmin=3)
    print('e=', e)
a= [3 4 5]
b= [3 4 5]
# ndmin的显式指定,4个纬度至少有4层中括号
c= [[[[3 4 5]]]]
d= [[3 4 5]
 [6 7 8]]
e= [[[3 4 5]
  [6 7 8]]]

2.复制、迭代器、指定范围等方式

numpy.asarray(a, dtype = None, order = None)
参数描述
a任意形式的输入参数,可以是,列表, 列表的元组, 元组, 元组的元组, 元组的列表,多维数组
dtype数据类型,可选
order可选,有”C”和”F”两个选项,分别代表,行优先和列优先,在计算机内存中的存储元素的顺序。
asarray 参数说明
numpy.fromiter(iterable, dtype, count=-1)
参数描述
iterable可迭代对象
dtype返回数组的数据类型
count读取的数据数量,默认为-1,读取所有数据
fromiter 参数说明

numpy.arange(start, stop, step, dtype)
参数描述
start起始值,默认为0
stop终止值(不包含)
step步长,默认为1
dtype返回ndarray的数据类型,如果没有提供,则会使用输入数据的类型。
arange参数说明
    # 5、通过复制已有数组来创建数组
    f = np.asarray(e, dtype=float)
    print('copy array = ', f)
    # 6、通过传入一个迭代器创建数组
    g = np.fromiter(iter(range(6)), dtype=float)
    print('fromiter = ', g)

    # 7、指定起始范围及步长的方式, 可正序可倒序
    h = np.arange(2, 10, 1.5, dtype=float)
    print('arange = ', h)
    h = np.arange(20, 10, -1.5, dtype=float)
    print('arange = ', h)
copy array =  [[[3. 4. 5.]
  [6. 7. 8.]]]
fromiter =  [0. 1. 2. 3. 4. 5.]
arange =  [2.  3.5 5.  6.5 8.  9.5]
arange =  [20.  18.5 17.  15.5 14.  12.5 11. ]

3.一些特殊的数组

numpy.empty(shape, dtype = float, order = 'C')
参数描述
shape数组形状
dtype数据类型,可选
order有”C”和”F”两个选项,分别代表,行优先和列优先,在计算机内存中的存储元素的顺序。
empty参数说明
numpy.zeros(shape, dtype = float, order = 'C')
参数描述
shape数组形状
dtype数据类型,可选
order‘C’ 用于 C 的行数组,或者 ‘F’ 用于 FORTRAN 的列数组
zeros参数说明
numpy.ones(shape, dtype = None, order = 'C')
参数描述
shape数组形状
dtype数据类型,可选
order‘C’ 用于 C 的行数组,或者 ‘F’ 用于 FORTRAN 的列数组
ones 参数说明
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
参数描述
start序列的起始值
stop序列的终止值,如果endpointtrue,该值包含于数列中
num要生成的等步长的样本数量,默认为50
endpoint该值为 true 时,数列中包含stop值,反之不包含,默认是True。
retstep如果为 True 时,生成的数组中会显示间距,反之不显示。
dtypendarray 的数据类型
linspace等差数列一维数组
np.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None)
参数描述
start序列的起始值为:base ** start
stop序列的终止值为:base ** stop。如果endpointtrue,该值包含于数列中
num要生成的等步长的样本数量,默认为50
endpoint该值为 true 时,数列中中包含stop值,反之不包含,默认是True。
base对数 log 的底数。
dtypendarray 的数据类型
logspace等比数列一维数组
 # 8、指定形状创建数组
    # 1.空数组
    a = np.empty((3, 3), dtype=float)
    print('empty=', a)
    # 2.元素全是0的数组
    b = np.zeros((3, 3), dtype=int)
    print('zeros=', b)
    # 3.元素全是1的数组
    c = np.ones((2, 3), order='F')
    print('ones=', c)
    # 4.等差数列的一维数组
    d = np.linspace(1, 15, 5, False)
    print('不要尾值,等差 d= ', d)
    d = np.linspace(1, 15, 5, True)
    print('保留尾值,等差 d= ', d)
    # 5.创建等比数列的一维数组
    e = np.logspace(1, 2, 4, base=2.0)
    print('e= ', e)
    e = np.logspace(0, 2, 4)
    print('e= ', e)
empty= [[0.00e+000 0.00e+000 0.00e+000]
 [0.00e+000 0.00e+000 1.86e-321]
 [0.00e+000 0.00e+000 0.00e+000]]
zeros= [[0 0 0]
 [0 0 0]
 [0 0 0]]
ones= [[1. 1. 1.]
 [1. 1. 1.]]
不要尾值,等差 d=  [ 1.   3.8  6.6  9.4 12.2]
保留尾值,等差 d=  [ 1.   4.5  8.  11.5 15. ]
e=  [2.        2.5198421 3.1748021 4.       ]
e=  [  1.           4.64158883  21.5443469  100.        ]