我要在网络上挖呀挖呀挖——爬虫技术

0 阅读8分钟

一、什么是爬虫

二、基础

(一)获取网页源代码

库:urllib

from urllib.request import urlopen
url = 'http://www.baidu.com'
response = urlopen(url)
print(response.read().decode('utf-8'))

(二)网页加载方式

  1. 静态页面,全部加载;

  2. 动态网页,数据和页面分开加载和请求。

三、requests模块

(一)安装

pip install requests

(二)使用

import requests
url = "http://www.baidu.com"
response = requests.get(url)
response.encoding = "utf-8"
print(response.text)

(三)变量访问与伪装

import requests
content = input("请输入要搜索的内容:")
url=f"http://www.baidu.com/s?wd={content}"
# 请求头,模拟浏览器访问
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url,headers=headers)
print(response.text)

使用headers自定义请求头部分,修改UA以模仿浏览器进行访问。

(四)post请求获取翻译信息

import requests
url = "https://fanyi.baidu.com/sug"
data = {
    "kw":input("请输入要翻译的单词:")
}
response=requests.post(url,data=data)
print(response.json())# 返回的是json格式数据,所以使用json()方法

post在data段包含发送的数据,一般返回json格式的数据。

(五)get请求获取信息

import requests
url= "https://movie.douban.com/j/chart/top_list"
data = {
    "type": 5,
    "interval_id": "100:90",
    "action": "",
    "start": 0,
    "limit": 20
}
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36"
}
response = requests.get(url, params=data, headers=headers)
print(response.json())

get请求时会将data段自动拼接到url后面。

(六)cookie处理

import requests
session = requests.Session()
url = "http://www.baidu.com"
data = {
    "username": "username",
    "password": "password"
}
response = session.post(url, data=data) 
print(response.text)
  1. 创建一个Session对象,可以在同一个Session对象中保持cookie等信息,但使用requests时会开启一个新的对象,不会保留cookie。

  2. 除了session还可以在headers直接写入cookie字段。

(七)防盗链

在headers中加入refer,规避溯源反扒机制。

(八)代理使用

import requests
url = "http://www.baidu.com"
proxy = {
    "http": "http://127.0.0.1:10809",
    "https": "http://127.0.0.1:10809"
}
r = requests.get(url, proxies=proxy)
print(r.text)

使用代理可以减少服务器封ip地址的情况,但速度较慢,费用较贵。

四、数据处理

(一)正则表达式

1. 元字符
符号含义
.匹配除换行符以外的任意字符
\w字母、数字、下划线
\s空白符
\d数字
\n换行符
\t制表符
字符串开头
$字符串结尾
\W非字母、非数字、非下划线
\D非数字
\S非空白符
a|ba或b
()分组,匹配括号内表达式
[……]匹配字符串中的字符,如a-z
[^……]匹配除字符串中字符的所有字符

2.量词

符号含义
*重复0次或更多次数
+重复1次或更多次数
?重复0次或1次
{n}重复n次
{n,}重复n次或更多次数
{n,m}重复n到m次
.*贪婪匹配,尽可能的匹配
.*?惰性匹配,尽可能少匹配

3. 使用:re库

  1. 提取所有符合的内容
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.findall(pattern, text)
print(result)
  1. 从迭代器获取所有符合的
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.finditer(pattern, text)
for match in result:# 从迭代器中获取每一个匹配对象
    print(match.group()) # 获取匹配的字符串
  1. 搜索第一个匹配的项
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.search(pattern, text)
print(result.group()) # 获取匹配的字符串
  1. 从字符串开头开始匹配(如果第一个字符不符合则失败)
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.match(pattern, text)
print(result) # 获取匹配的字符串
  1. 预加载
import re
object = re.compile(r"\d{10}")
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
result1 = object.findall(text)
result2 = object.finditer(text)
result3 = object.search(text)
print(result1)
print(result2)
print(result3.group())
  1. 提取多个内容

<>内写你取得名称来区别

import re
object = re.compile(r"<div class=\"(?P<class>.*?)\">(?P<content>.*?)</div>")
s = """
<div class="title">这是一个标题</div>
<div class="content">这是一个内容</div>
"""
result = object.finditer(s)
for item in result:
    class_name = item.group("class")
    content = item.group("content")
    print(f"类名:{class_name},内容:{content}")

(二)使用BeautifulSoup处理

from bs4 import BeautifulSoup
html = """
<div class="title">这是一个标题</div>
<div class="content">这是一个内容</div>
"""
page = BeautifulSoup(html, "html.parser")
div = page.find("div", class_="title")
print(div.text)
divs = page.find_all("div")
classes = page.find_all("div", class_=True)
print(class_names)
for div in divs:
    print(div.text)
    print(div.get("class"))

find只找一个符合的。

文本用.text,图片等用.content

(三)xpath处理(lxml)

from lxml import etree
xml = """
<books>
<id>1</id>
<author>
<nickname>Neo</nickname>
<nickname>小王子</nickname>
<nickname class="1">Python爬虫基础</nickname>
</author>
<price>100</price>
<name>Python爬虫基础</name>
</books>   
"""
tree = etree.XML(xml)
et = tree.xpath('//nickname/text()') 
result = tree.xpath('//nickname')# 获取下级所有节点

for item in result:  
    print(item.text)
one = tree.xpath('//nickname[@class="1"]/text()')[0]
  1. text() 获取节点内容,默认是列表,如果取出第一个元素,可以使用[0];

  2. //表示所有节点,/表示根节点;

  3. @class="1"获取属性为class的值为1的节点内容。

(四)pyquary

from pyquery import PyQuery as pq
html = """
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
"""

doc = pq(html)
a = doc("li")
b = doc("li a")
c = doc("li a span")
class_names = doc("li").attr("class") 
item_text = doc("li").text() 
print(class_names)
print(item_text)
print(a)
print(b)
print(c)
  1. attr("class")获取第一个li标签的class属性值,多个标签会返回第一个标签的属性值,如果想获取所有标签的属性值,可以使用items()方法;

  2. text()获取所有li标签的文本内容,多个标签会返回所有标签的文本内容;

  3. pyquery中就是直接用css选择器,注意返回的不是字符串,而是pyquery对象。

from pyquery import PyQuery as pq
html = """
<div class="title">这是一个标题</div>
<div class="content">这是一个内容</div>
"""
doc = pq(html)
after = doc(".title").after("<div class='after'>这是一个after标签</div>")
before = doc(".title").before("<div class='before'>这是一个before标签</div>")
print(doc)
remove = doc(".title").remove()
remove_attr = doc(".content").remove_attr("class")
print(doc)
add_class = doc(".after").add_class("new-class")
add_attr = doc(".after").attr("data-id", "123")   
print(doc)
  1. after()before()在.title标签后面、前面添加一个新的标签;

  2. remove()删除标签;

  3. remove_attr("class") 删除标签的class属性;

  4. add_class("new-class")给标签添加一个新的class属性值;

  5. attr("data-id", "123") 给标签添加一个新的属性data-id,值为123,如果属性已经存在,会覆盖原有的属性值。

五、提高效率

(一)多线程

from threading import Thread
def task():
    for i in range(5):  
        print(f"线程任务执行第 {i+1} 次")
thread = Thread(target=task) # 创建一个线程对象,target参数指定线程要执行的函数
thread.start() # 启动线程,线程开始执行任务
for i in range(5):
    print(f"主线程执行第 {i+1} 次")

通过Thread传递参数给函数时使用arg=()括号内必须为元组,如果只有一个变量,要在后面加逗号。

(二)多进程

from multiprocessing import Process
def task():
    for i in range(5):
        print(f"子进程任务执行第 {i+1} 次")
pro = Process(target=task) # 创建一个进程对象,target参数指定进程要执行的函数
pro.start() # 启动进程,进程开始执行任务
for i in range(5):
    print(f"主进程执行第 {i+1} 次")

(三)线程池

from concurrent.futures import ThreadPoolExecutor
def task(n):
    print(f"线程任务执行第 {n} 次")
with ThreadPoolExecutor(max_workers=5) as executor: # 创建一个线程池,max_workers参数指定线程池中线程的最大数量
    for i in range(40):
        executor.submit(task, i+1) # 提交任务到线程池,参数通过submit方法传递
print(f"主线程提交了第 {i+1} 个任务")

(四)协程

import asyncio
async def task(n):
    for i in range(10):
        print(f"异步任务执行第 {n} 次,第 {i+1} 次")
        await asyncio.sleep(1) # 模拟异步任务的耗时操作,可以使用asyncio.sleep()来实现非阻塞的睡眠
async def task2(n):
    for i in range(10):
        print(f"异步任务2执行第 {n} 次,第 {i+1} 次")
        await asyncio.sleep(1)
async def task3(n):
    for i in range(10):
        print(f"异步任务3执行第 {n} 次,第 {i+1} 次")
        await asyncio.sleep(1)
await asyncio.gather(task(1), task2(2), task3(3)) # 使用asyncio.gather()来并行执行多个异步任务

或者:

import asyncio
async def task(n):
    for i in range(10):
        print(f"异步任务执行第 {n} 次,第 {i+1} 次")
        await asyncio.sleep(1) # 模拟异步任务的耗时操作,可以使用asyncio.sleep()来实现非阻塞的睡眠
async def task2(n):
    for i in range(10):
        print(f"异步任务2执行第 {n} 次,第 {i+1} 次")
        await asyncio.sleep(1)
async def task3(n):
    for i in range(10):
        print(f"异步任务3执行第 {n} 次,第 {i+1} 次")
        await asyncio.sleep(1)
async def main():
    tasks = [asyncio.create_task(task(1)), asyncio.create_task(task2(2)), asyncio.create_task(task3(3))] # 创建一个包含多个异步任务的列表
    await asyncio.wait(tasks) # 等待所有异步任务完成
if __name__ == "__main__":
    asyncio.run(main()) # 运行主函数,启动事件循环,执行异步任务

aiohttp

import asyncio
import aiohttp
async def fetch(url):
    async with aiohttp.ClientSession() as session: # 创建一个异步HTTP会话
        async with session.get(url) as response: # 发送GET请求,获取响应对象
            return await response.text() # 获取响应内容,使用await等待异步操作完成
async def main():
    url = "http://www.baidu.com"
    content = await fetch(url) # 调用fetch函数,获取网页内容,使用await等待异步操作完成
    print(content)
await main() # 在Jupyter Notebook中直接调用,不需要asyncio.run()