从0开始学 Python 爬虫(更新中)

916 阅读5分钟

简介

从0开始学习 Python 爬虫
项目源码:gitee.com/Actoress/sp…

爬虫数据采集分类

按照采集对象分类

  • 全网采集
  • 全站采集
  • 具体网站的指定数据采集

按照采集方案分类

  • 利用http协议采集 - 页面分析
  • 利用api接口采集 - app数据采集
  • 利用目标网站的api采集 - 微博 github twitter facebook

常用正则表达式

. :匹配任意字符(不包括换行符)
^ :匹配开始位置,多行模式下匹配每一行的开始
$ :匹配结束位置,多行模式下匹配每一行的结束
* :匹配前一个元字符0到多次
+ :匹配前一个元字符1到多次
? :匹配前一个元字符0到1次
{m, n} :匹配前一个元字符m到n次
\\ :转义字符,跟在其后的字符将失去作为特殊元字符的含义,例如\\.只能匹配,
[] :字符集,一个字符的集合,可匹配其中任意一个字符
| :逻辑表达式, 比如a|b代表可匹配 a 或者 b
\b :匹配位于单词开始或结束为止的空字符串
\B :匹配不位于单词开始或结束为止的空字符串
\d :匹配一个数字,相当于[0-9]
\D :匹配非数字,相当于[^0-9]
\s :匹配任意空白字符,相当于[ \t\n\r\f\v]
\S :匹配非空白字符,相当于[^ \t\n\r\f\v]
\w :匹配数字、字母、下划线中任意一个字符,相当于[a-zA-Z0-9]
\W :匹配非数字、字母、下划线中任意字符,相当于[^a-zA-Z0-9]

使用beautifulsoup4

官方文档:beautifulsoup.readthedocs.io/zh_CN/v4.4.…

首先安装beautifulsoup4

pip3 install beautifulsoup4

引入beautifulsoup4

from bs4 import BeautifulSoup

使用find()

import re
from bs4 import BeautifulSoup

html = """
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>淘趣指数</title>
  </head>
  <body>
    <div id="div1" class="div1 div">
        <span class="span1">
            div1-span1
            <i>div1-span1-i1</i>
        </span>
        <span>div2-span2</span>
    </div>
    <div id="div2" class="div2 div">测试用div文本2</div>
    <div id="div3" class="div3 div">测试用div文本3</div>
    <div id="div4" class="div4">测试用div文本4</div>
    <div id="div5" class="div5">测试用div文本5</div>
    <p>测试用文本2</p>
  </body>
  </body>
</html>
"""

bs = BeautifulSoup(html, "html.parser") # 参数1:需要解析的字符串,参数2所使用的解析库(文档中可查)

# 获取 title
title_tag = bs.title  # 获取到 bs 中自定义的 tag 类
title_con = title_tag.string  # 获取 tag 中的内容


# 获取div
div_tag = bs.find("div")  # 返回第一条满足要求的元素
div_tags = bs.find_all("div")  # 返回所有满足要求的元素,返回对象为一个数组
for item in div_tags:
    print(item.string)


# 通过 id 获取
div_tag_id1 = bs.find(id="div5")  # 获取 id=div5 的元素
div_tag_id2 = bs.find("div", id="div4")  # 获取 id=div4 的 div
div_tag_re = bs.find_all("div", id=re.compile("div\d"))  # 通过正则表达式获取


# 通过 class 获取
div_tag_class1 = bs.find("div", {"class": "div1"})


# 通过内容获取
div_tag_str = bs.find(string="测试用div文本1")  # 返回的是 NavigableString 类型


# 获取子节点
parent = bs.find(id="div1")
child1 = parent.contents  # 获取子节点
child2 = parent.descendants  # 获取子节点的子节点
for item in child2:
    # 因为纯在换行符所以需要判断name
    if item.name:
        print(item.name)


# 获取父节点
parent = bs.find("span", {"class": "span1"}).parent
# 获取所有祖先节点
parents = bs.find("span", {"class": "span1"}).parents


# 获取兄弟节点
next_tag = bs.find("span", {"class": "span1"}).next_sibling  # 获取之后的一个兄弟节点
next_tags = bs.find("span", {"class": "span1"}).next_siblings  # 获取之后的所有兄弟节点
previous_tag = bs.find("span", {"class": "span1"}).previous_sibling  # 获取之前的一个兄弟节点
previous_tags = bs.find("span", {"class": "span1"}).previous_siblings  # 获取之前的所有兄弟节点


# 获取节点的属性
div_tag = bs.find("div", {"class": "div1"})
print(div_tag["class"])
print(div_tag.get("class"))

使用 Requests 爬虫

官方文档:2.python-requests.org/zh_CN/lates…

使用 Scrapy 爬虫

安装 Scrapy

pip3 install lxml  # 必备依赖
pip3 install Scrapy

引入 Selector

from scrapy import Selector

Selector 操作 xpath

import requests
from scrapy import Selector

html = requests.get("https://www.hao123.com/")
html_text = html.text

sel = Selector(text=html_text)  # 初始化

tag = sel.xpath('//*[@id="sites2_wrapper"]/div[3]/ul/li[4]/div/a')
print(tag.extract()[0])  # 获取标签

text = sel.xpath('//*[@id="sites2_wrapper"]/div[3]/ul/li[4]/div/a/text()')
print(text.extract()[0])  # 获取内容

Selector 操作 css 选择器

import requests
from scrapy import Selector

html = requests.get("https://www.hao123.com/")
html_text = html.text

sel = Selector(text=html_text)  # 初始化

wrapper_class = sel.css(".wrapper")
print(wrapper_class.extract()[0])  # 获取标签
wrapper_id = sel.css("#wrapper::text")
print(wrapper_id.extract()[0])  # 获取内容

GIL

什么是GIL

GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。 每个CPU在同一时间只能执行一个线程(在单核CPU下的多线程其实都只是并发,不是并行)

某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是"通行证",并且在一个python进程中,GIL只有一个。拿不到通行证的线程 ,就不允许进入CPU执行

GIL会释放的:

  • 1.时间片释放 - 指定时间释放
  • 2.遇到IO释放

GIL的作用:

  • GIL保证安全,但是GIL又有时间片的概念
  • GIL会保证字节码的安全

【重要】线程间的同步

目的:希望我这个线程在执行的过程中其它的线程不要执行,等待我这个线程执行完成后,再继续执行后面的线程

应用场景:电商库存投票票数

实例代码

"""
    python 多线程爬虫学习

    线程间的同步:
        当我们运行代码时,不希望代码被中断
"""
from threading import Thread
from threading import Lock

total = 0

# 建立一个通用线程锁,注意使用同一把锁,如果有一个线程获取到了这把锁,另一个线程就必须等待这个锁释放
total_lock = Lock()


def add():
    total_lock.acquire()  # 加锁
    global total
    for i in range(1000):
        total += 1
    total_lock.release()  # 释放锁


def desc():
    total_lock.acquire()  # 加锁
    global total
    for i in range(1000):
        total -= 1
    total_lock.release()  # 释放锁


if __name__ == "__main__":

    # 线程传递参数
    t1 = Thread(target=add)
    t1.start()

    t2 = Thread(target=desc)
    t2.start()

    t1.join()  # t1 运行完成后才会执行之后的逻辑
    t2.join()  # t2 运行完成后才会执行之后的逻辑

    print(total) # => 0

【重要】线程间的通信