爬虫常用模块笔记

urllib库

urllib库四大模块:

  • urllib.request 请求模块
  • urllib.error 异常处理模块
  • urllib.parse url解析模块
  • urllib.robotparser robots.txt解析模块

urlopen 和响应

import urllib.request  

response = urllib.request.urlopen('https://www.python.org')    # get请求
print(type(response))    # <class 'http.client.HTTPResponse'>
print(response.status)   # 200
print(response.getheaders())  #[('Connection', 'close'), ('Content-Length', '51141'), ('Server', 'nginx'),...]
print(response.getheader('Server'))  # nginx
print(response.read().decode('utf-8'))
import urllib.parse
import urllib.request

data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')  #post请求
response = urllib.request.urlopen('http://httpbin.org/post', data=data)

print(response.read().decode("utf-8"))
import socket
import urllib.request
import urllib.error

try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)  # timeout参数
except urllib.error.URLError as e: 
    print(type(e.reason))    #   <class 'socket.timeout'>
    if isinstance(e.reason, socket.timeout):  
        print('TIME OUT')

Request

from urllib import request, parse

url = 'http://httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    'Host': 'httpbin.org'
}
dict = {
    'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

requests库

import requests  # 导入模块

r = requests.get('https://www.baidu.com/')   # 返回Response对象 <class 'requests.models.Response'>
Response对象常用属性
r.request.headers        # 请求头信息 重要 User-Agent身份标识,Referer本次请求从哪个页面来的(防盗链),Cookie本地字符串数据信息
r.headers                # 响应头信息 重要 cookie,各种神奇的莫名其妙的字符串(一般是token字样),防止攻击和翻爬
r.status_code            # 响应状态码
r.encoding               # 获取或者设置当前的编码
r.apparent_encoding      # 从内容分析出的编码方式
r.raise_for_status()     # 失败请求(非200响应)抛出异常
r.text              # 返回字符串内容,等同r.content.decode(r.encoding)    <class 'str'>
r.content                # 以字节形式(二进制)返回 。可以把图片写入文件               <class 'bytes'>
r.json()         # 若返回的是json字符串,用此可以把返回转换为字典, {'args': {}, 'headers': {'Accept': '*/*',....}} 如果不是json格式,产生异常
爬取网页通用代码框架
def getHtmlText(url):
    try:
        r = requests.get(url, timeout=30)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except:
        return "产生异常"
    r.close()   #如果不关闭,多次请求可能报错

get方法

headers = {"User-Agent":"...", 'Cookie':'...',"Referer":"..."} #cookie的字符串从开发者工具中获取
proxies = {"https":"https://218.90.33.83:3128"} #代理,Referer防盗链
params={"wd":"python"}
response = requests.get("http://www.baidu.com", params=params, headers=headers, cookies=cookies,proxies=proxies)
# cookies字典从f12工具中cookie字符串加工而来,先以"; "切割,再遍历用等号切割,切割次数为1,得到键值对
# params 等同于带参数的访问(百度搜索python)"http://www.baidu.com/s?wd=python" 
# 对https类网址,加verify=False,去掉安全验证

post方法

data = {'name': 'germey', 'age': '22'}
response = requests.post(url,data=data)

post数据来源(data到抓包工具里面获取)
1.固定值 2.输入值 3.预设值-静态文件(需要提前从静态文件中读取)
4.预设值-发请求(需要对指定地址发请求)
5.在客户端生成的 (分析js,模拟生成数据)

文件上传

import requests

files = {'file': open('favicon.ico', 'rb')}
response = requests.post("http://httpbin.org/post", files=files)
print(response.text)

获取cookie

response = requests.get("https://www.baidu.com")
print(response.cookies)                     # <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
for key, value in response.cookies.items():
    print(key + '=' + value)                 # BDORZ=27315

会话保持

session = requests.session()  #会话,记得刚才的谈话内容
data = {"name":"..","password",".."}
session.post(url, data=data)  #返回的cookie记住
resp = session.get(....)      #发送请求时自动发送了cookie
#用session是一种办法,另一种办法是headers里面加cookies值

异常处理

import requests
from requests.exceptions import ReadTimeout, ConnectionError, RequestException
try:
    response = requests.get("http://httpbin.org/get", timeout = 0.5)
    print(response.status_code)
except ReadTimeout:
    print('Timeout')
except ConnectionError:
    print('Connection error')
except RequestException:
    print('Error')

数据解析

三种比较,re最快,xpath最简单,bs4

re 正则表达式

正则的语法:使用元字符进行排列组合用来匹配字符串

常用元字符:

. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
\w 匹配字母或数字或下划线,等同[A-Za-z0-9_] \W 等同[^A-Za-z0-9_]
\s 匹配任意的空白符,包括空格、制表符、换页符、换行,等价于 [\t\n\r\f]. \S 非空白符,不包括换行
\d 匹配数字 \D 匹配非数字
\n 匹配一个换行符
\t 匹配一个制表符

^ 匹配字符串的开始
$ 匹配字符串的结束

a|b 匹配字符a或者字符b
() 匹配括号内的表达式,也表示一个组
[...] 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k'
[^...] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。

量词(控制前面元字符出现的次数):

*重复零次或更多次
+重复一次或更多次
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式

贪婪匹配和惰性匹配

.* 贪婪匹配
.*? 惰性匹配,非贪婪

re模块方法

  1. findall 查找所有,返回list
    re.findall(r"\d+","5点之前,要给我5000万")   # 得到['5','5000'],如果里面有分组项,只返回元组组成的列表,每个元组由分组项组成
    
  2. search 返回第一个匹配结果,如果没有匹配,返回None
    re.search(r"\d+","5点之前,要给我5000万",re.S).group()   # 得到'5'
    
  3. match 只能从字符串的开头进行匹配
content = 'Hello 1234567 World_This is a Regex Demo'   # 对正则表达式里面用到的字符,需要转义,比如匹配字符串里面的.需要写成\.,$需要写成\$
result = re.match('^He.*(\d+)\sWorld(.*)Demo$', content, re.S)   # .*为贪婪模式,匹配 "llo 123456",如果是.*?,匹配"llo ",无re.S,逐行找,有re.S把多行字符串当作一个字符串
print(result)           # <re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
print(result.group(0))  # Hello 1234567 World_This is a Regex Demo   # group() 等同于 group(0)
print(result.group(1))  # 贪婪模式 7,非贪婪模式  1234567
print(result.group(2))  # _This is a Regex 
print(result.span())    # (0, 40)
# 总结:尽量使用泛匹配、能用search就不用match、使用括号得到匹配目标、尽量使用非贪婪模式、有换行符就用re.S
  1. sub 替换字符串中每一个匹配的子串后返回替换后的字符串。
content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
content = re.sub('\d+', 'Replacement', content)
print(content)  # Extra stings Hello Replacement World_This is a Regex Demo Extra stings
  1. 预加载正则表达式
    str_ = "我叫孙文平,我今年56岁了。"
    obj = re.compile(r'我叫(?P<name>.*?),我今年(?P<age>.*?)岁了')  # 如果用re.S参数,要加到这里,否则出错
    obj.search(str_)  # 或者 re.search(obj, str_)    
    # 假如返回对象赋值给变量m,m.group("name"),或者m.group(1),得到“孙文平” 
    # 或者m.groupdict() 得到k:v 字典
    

BeautifulSoup4库

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')  # # 还有'html.parser' , 'xml' ,'html5lib' 解析器 
print(soup.title)       # <title>The Dormouse's story</title>  选择元素
print(type(soup.title))  # <class 'bs4.element.Tag'>
print(soup.title.name)   # title                          获取名称
print(soup.p.attrs['name'])  #  dromouse      获取属性
print(soup.p['name'])        #  dromouse
print(soup.p.string)         # The Dormouse's story     获取内容
print(type(soup.p.string))   # <class 'bs4.element.NavigableString'>
print(soup.find_all('ul'))   # 返回两个ul标签组成的列表 
print(soup.find_all(attrs={'id': 'list-1'}))   # 返回所有id="list-1"的标签的列表
print(soup.find_all(attrs={'name': 'elements'}))  # 返回所有name='elements'的标签的列表
print(soup.find_all(id='list-1'))    # 返回所有id="list-1"的标签的列表
print(soup.find_all(class_='element'))   # 返回所有class='element'的标签的列表,因为class为关键字,故加上下划线
soup.select("a")  通过标签名查找
soup.select(".sister")  通过类名查找,查找class=sister的标签
soup.select("#link1")  通过id查找,查找id="link1"的标签
soup.select('a[href=''.....]')  通过属性查找
soup.select("p #link1 a")  组合查找,p标签id为link1的a标签
# 推荐使用lxml解析库,必要时使用html.parser
# 建议使用find()、find_all() 查询匹配单个结果或者多个结果

Xpath库

安装lxmo, pip install lxml

from lxml import etree
tree = etree.XML(xml)  #HTML,parse解析文件
tree.xpath("/html/*/a/text()")[0]    #/表示层级关系,第一个/是根节点,*表示这级类型任意,text()取a标签的文本,返回一个列表,一般取第一个[0]
tree.xpath("//a/@href")  #//表示查找各个层级的a标签,@href a标签中href的值
tree.xpath("/html/body/ul/li[2]/a/text()")[0] #ul下第二个li标签,text()取文本
tree.xpath("/html/body/ul/li[2]/a/@href")[0] #ul下第二个li标签下的a标签,取其href属性,得到一个列表,取第一个元素
tree.xpath("/html/body/ul/li/a[@href='dabao']/text()") #href='dapao'的a标签
li.xpath("./a/text()")  #li是xpath查找到的li节点,在li标签内查找a标签

pyquery库

from pyquery import PyQuery as pq
doc = pq(html)  # html 可以是字符串,url(url="..."), 文件名(filename="...")。doc是PyQuesy对象

基本css选择器及查找节点(返回的都是PyQuesy对象,返回的若是多个节点,用.items()转换后遍历)

  • css选择器:参见BeautifulSoup库的select方法,返回的也是PyQuesy对象
for item in doc('#container .list li').items(): #id为container下的,class为list下的,li标签。
  print(item.text())     #items()转换为 generator object,可遍历对象
  • 查找子节点
    用find(查找子孙节点)或者children(查找子节点)方法,传入的是css选择器,返回也是PyQuesy对象
    PyQuesy对象.find('li') PyQuesy对象.children('li')
  • 查找父节点
    用parent方法获取某个节点的父节点, parents方法获取祖先节点(可以传入css选择器)
    PyQuesy对象.parent(), PyQuesy对象.parents('.wrap')
  • 查找兄弟节点
    用siblins获取兄弟节点
    PyQuesy对象.siblings('.active')
    获取信息
  • 获取属性
    单个PyQuesy对象.attr(属性名称) a.attr('href') 也可以写成 a.attr.href
    多个节点的PyQuesy对象,只返回第一个节点的属性,所以只能用.items()转换后遍历,再获取属性
  • 获取文本
    a.text() 获取节点的(包括子节点的)文本,多个节点的对象返回所有节点的text文本,中间用空格隔开
    a.html() 获取节点的html文本,多个节点的对象只返回第一个

节点操作

  • removeclass addclass
  • attr(属性名) 是获取属性,attr(属性名,字符串) 将改变属性或者增加属性,同样的还有text,html方法
  • remove() 方法 移除 此节点

伪类选择器

  • 第一个节点 doc("li:first-child")
  • 最后一个节点 doc("li:last-child")
  • 第二个节点 doc("li:nth-child(2)")
  • 包含某一个文本的节点 doc("li: lontains(second)")

selenium

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

browser = webdriver.Chrome()
try:
    browser.get('https://www.baidu.com')
    print(browser.page_source)
    print(browser.current_url)
    print(browser.get_cookies())
    input = browser.find_element(By.ID,"kw")              # find_element 返回找到的第一个元素
    input = browser.find_element(By.CLASS_NAME,"mnav")    
    input = browser.find_element(By.TAG_NAME,"title")     
    input = browser.find_element(By.LINK_TEXT,"新闻")
    input = browser.find_element(By.PARTIAL_LINK_TEXT,"闻")
    input = browser.find_element(By.NAME,"tj_login")
    input = browser.find_element(By.XPATH,'//*[@id="u"]/a[3]')  # chrom xpath 调试器 控制台 $x('xpath代码')
    # /绝对路径 //相对路径  *任意标签 索引从1开始 @属性名  多属性定位 //input[@id="kw" and @class='kw-class'] 
    # 函数 文本 //a[text()="百度"] 文本包含 //a[contains(text(), "百度")] 文本开头 //a[starts-with(text(), "百度"]
    # 最后一个, 如//div[last()]
    input = browserr.find_element(By.CSS_SELECTOR,".s_ipt") # class用. id用#  name='wd' 子级"span>input.s_ipt" 子孙级 空格
    
    input = browser.find_elements(By.NAME,"tj_login")   # find_elements 返回列表
    print(input[1].text)                               # 获取文本值,如果隐藏用下面语句,其他input.id,location,tag_name,size等
    print(input[1].get_attribute("textContent"))       # 获取属性:get_attribute("class")  
    
    input.send_keys('Python')
    input.send_keys(Keys.ENTER)
    input.clear()
    button = browser.find_element_by_class_name('btn-search')
    button.click()
except TimeoutException:  # 超时异常
    print('Time Out')
except NoSuchElementException: # 元素找不到异常
    print('No Element')
finally:
    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()

执行javasript

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')  #滚动条到最下
browser.execute_script('alert("To Bottom")')           # 弹出对话框

Frame

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException

browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')                    # 切换到子 Frame中
source = browser.find_element_by_css_selector('#draggable')
print(source)
try:
    logo = browser.find_element_by_class_name('logo')    # 尝试找父Frame中的元素
except NoSuchElementException:
    print('NO LOGO')
browser.switch_to.parent_frame()                        # 切换回父Frame
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)

窗口之间的切换(选项卡)

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')  # 打开一个新的选项卡,并切换到到新的选项卡
browser.get('https://www.taobao.com')    # 新选项卡打开淘宝
time.sleep(1)
browser.switch_to_window(browser.window_handles[0])  # 返回到第一个选项卡,但没有在前显示
browser.get('https://python.org')
iframe切换(切换到子iframe中是无法查找到父frame中的元素的)
    iframe = web.find_element_by_xpath('...')
    web.switch_to.frame(iframe)  # 切换到iframe
    web.switch_to.default_content() #切换回原页面(web.switch_to.parent_frame())

前进后退

browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.get('https://www.taobao.com/')  # 在同一个选项卡中顺次打开两个网页
browser.back()                          # 返回到百度网页
time.sleep(1)
browser.forward()                      # 返回到淘宝网页
browser.close()

Cookies

web.get_cookies()
web.add_cookies({字典})
web.delete_all_cookies()

等待

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

browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
# browser.implicitly_wait(10) # 隐式等待
wait = WebDriverWait(web,10)   # 显式等待,创建对象,最长等待时间10
input = wait.until(EC.prsence_of_element_located((By.ID,'q')))  # 元素加载完毕
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-search'))) # 元素可点击
'''
title_is 标题是某内容
title_contains 标题包含某内容
presence_of_element_located 元素加载出,传入定位元组,如(By.ID, 'p')
visibility_of_element_located 元素可见,传入定位元组
visibility_of 可见,传入元素对象
presence_of_all_elements_located 所有元素加载出
text_to_be_present_in_element 某个元素文本包含某文字
text_to_be_present_in_element_value 某个元素值包含某文字
frame_to_be_available_and_switch_to_it frame加载并切换
invisibility_of_element_located 元素不可见
element_to_be_clickable 元素可点击
staleness_of 判断一个元素是否仍在DOM,可判断页面是否已经刷新
element_to_be_selected 元素可选择,传元素对象
element_located_to_be_selected 元素可选择,传入定位元组
element_selection_state_to_be 传入元素对象以及状态,相等返回True,否则返回False
element_located_selection_state_to_be 传入定位元组以及状态,相等返回True,否则返回False
alert_is_present 是否出现Alert
'''

无头浏览器(后台运行浏览器)

    from selenium.webdriver.chrome.options import Options
    
    opt = Options()
    opt.add_argument("--headless") # 无头
    opt.add_argument("--disbale-gpu") # 没有图形界面
    web = Chrome(options=opt)

线程 进程 协程 线程池

进程是资源单位,每个进程至少要有一个线程,线程是执行单位

线程(两种办法)
from threading import Thread # 多线程
from multiprocessing import Process  # 多进程

面向过程

def func(str_):
    for i in range(1000):
        print(str_, i)
        
t = Thread(target=func, args=("sun",))  # 参数必须是元组
t.start()     #  多线程状态为可以开始工作,具体执行时间由cpu决定
for i in range(1000):
    print("main", i)

面向对象

class MyThread(Thread):
    def __init__(self, str_):
#         super().__init__()   # 两种父类初始化方法
        Thread.__init__(self)
        self.str_ = str_
    def run(self):     # run名字固定,当线程执行时执行本方法
        for i in range(1000):
            print(self.str_, i)
            
t = MyThread("sun")
t.start()     # 不能写成t.run(),否则就是单线程
for i in range(1000):
    print("main", i)

线程池(进程池)

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def func(str_):
    for i in range(1000):
        print(str_, i)
        
with ThreadPoolExecutor(10) as t:
    for i in range(1000):
        t.submit(func,f"sun{i}") # 位置参数,同普通方法
# 等待线程池中的任务全部执行完毕,才继续执行(守护)   
print("end")

另一种多进程

import multiprocessing

p = multiprocessing.Pool(3) # 创建有3个进程的进程池
for i in range(10):
  p.apply_async(函数名,args=(参数,))  # 非阻塞执行,apply是阻塞执行
p.close()   # 关闭Pool,不再接收新的任务
p.join()   # 主进程阻塞,等待子进程的退出

协程

协程:当程序遇见阻塞操作(io类,比如input,网络get请求,写文件)的时候,可以选择性的切换到其他任务上(time.sleep是同步,不能切换)
多任务异步操作,当然多个协程都是在一个单线程里面

  1. 入门
    import asyncio

异步执行的多个函数前面加 async 关键字,f1=func1(),得到一个协程对象,而不是执行函数
tasks =[f1, f2, f3],将协程对象放入任务列表中
asyncio.run(asyncio.wait(tasks)) 启动执行
time.sleep()是同步,asyncio.sleep()是异步,而且应写成 wait asyncio.sleep()

  1. 协程程序框架
    import asyncio
    import time

    async def func1():
        print("sun wen ping")
        await asyncio.sleep(3)
        print("sun wen ping")

    async def main():
        tasks = [f1(), f2(), f3()] #python3.8版本之后,写成asyncio.create_task(func1())
        await asyncio.wait(tasks)

    if __name__ == "__main__":
        t = time.time()
        asyncio.run(main())
        print(time.time() - t)
  1. 爬虫协程框架
    import aiohttp
    import asyncio

    urls = ["...","...","..."]

    async def aiodownload(url):
        name = url.rsplit('=', 1)[1] + ".jpg"
        async with aiohttp.ClientSession() as session: # session 等同于 request
            async with session.get(url) as resp:
                with open(name, "wb") as f:
                    f.write(await resp.content.read()) # 读取是异步的,需要await挂起

    async def aiomain():
        tasks = []
        for url in urls:
            tasks.append(aiodownload(url)) # 3.8版本后asyncio.create_task(func1())
        await asyncio.wait(tasks)

    if __name__ == '__main__':
        asyncio.run(aiomain())
posted @ 2021-09-07 02:55  越自律越自由  阅读(75)  评论(0)    收藏  举报