爬虫 – Web Spider

定义: 爬虫/Spider/信息采集
流程: 获取网页
分析提取
保存信息
自动化程序

HTTP基本原理

URI和URL

URI(Uniform Resource Identifier)统一资源标志符
URL(Universal Resource Locator)统一资源定位符

URL是URI的子集,URI还包括一个子类URN(Universal Resource Name)统一资源名称,URN只命名资源不指定如何定位资源

超文本(hypertext)

网页源代码,html代码
查看源代码工具和方法

HTTP和HTTPS

HTTP(Hyper Text Transfer Protocol)
超文本传输协议

http与https的区别
Https是网络安全的协议,http是不安全的协议
Https默认端口是443,http是80
https是建立在安全套阶层(ssl)基础之上,所以是可靠的
安装https需要证书,分公钥和私钥,申请证书需要专门的机构才能下发
当然购买的话可以在阿里云上购买,一年大概2000以上,每天都要付费

系统的性能排查

普通单个页面打开需要在2秒之内,首页一般需要控制在1秒左右
网络速度是否正常 ping,检查下路由的路径
网络资源加载速度
服务器和服务器之间(比如说django服务器和数据库服务器之间的通信速度和带宽)

Put方法和Post方法有什么区别?

Post是新增操作
Put是修改操作
同样的表单信息,用post提交和用put提交哪一个会有变化? — Post

mbp是什么? – macbook pro

为什么会有cookie,cookie跟session有什么区别?
http本身是无状态的,用cookie可以在不同的请求之间交换一个状态,比如说
用户id信息,达到识别用户的目的。cookie里面不要放太多信息和敏感信息。
Cookie是保存在客户端的,session是保存在服务端的。
比如说:django项目里面mysql数据库里有一个django_session表,这个表里面有一个session_id字段和value字段。这个session_id的值是同时保存在客户端cookie里面.然后每次请求的时候,通过发送这个session_id到服务端,服务端通过查找对应session_id的value找到session里面具体存放的东西。

image.png
image.png

你们的web服务器对500错误是如何处理的?
500错误是内部服务器错误,我们会做一个统一错误捕获和处理的中间件,然后我们一般显示给用户说‘系统繁忙,请稍后再试’,千万django配置里面不能把debug设为true

普通爬虫的开发步骤

分为 获取网页、分析网页、储存数据、自动化 4个步骤

你用的那个包获取的网页
requests 获取的网页,requests.get()方法返回一个响应对象
response.concent返回的是什么?
返回的是字节流(二进制)转换成字符串需要用response.content.decode(‘utf-8’)

解析网页的方法主要有三种:re、xpath、css

在学习爬虫的过程中,要总结你做的爬虫的所有项目(用excel记录)
你爬的是哪个网站,这个网站做什么的(猫眼,买电影票的)
你爬取的数据量有多大(总共10页,每页10条,共100条数据)
你爬取的频率(每天自动爬取一次(todo))
遇到的反爬有什么,如何解决的(user-agent)
还遇到了什么问题,如何解决的

补充知识:
关系型数据库
开源
MySql,postgresql
企业
Sql server – 微软
oracle – 甲骨文
db2
sybase

Windows Server 2012/2008

开源数据库
Mysql community enterprise
mysql -u root -p

mysql存储引擎
Mylsam 适合与查询,不支持事务
lnnodb 支持事务(mysql默认)

事务:多个操作要么同时完成,要么都不完成

我们用requests获取的网页是从服务端直接拉下来的,没有经过浏览器的前端渲染,就是js并没有运行,所以你在开发者工具的页面元素查看中看到的东西不一定有,需要到开发者工具的网络栏里面去看实际的页面长什么样子。如果实际页面里没有内容,那一般都是通过ajax调用动态加载的数据,然后用js渲染到页面上的。ajax的请求我们一般通过xhr和js两个栏目来定位

请你描述一下MTV或者MVC模式
M – Model 模型
T – Template 模板 做一个展现(可以展现在手机、pc、大屏都可以,做自适应bootstrap)
V – View 视图 通过存取model的数据然后再把数据丢给template,view里面可以写跟数据和响应数据有关的逻辑。如果代码太多,我们需要新建helper或util进行代码封装

model 是拿来做什么的,没有行不行?
model是用来跟数据库进行交互的,做一个数据的存取。没有他其实是可以的,我们可以用pymysql也可以做,但是为什么我们还要他呢?
我们用model来做一个面向对象的操作,为不是sql语句
model帮我们隔离了具体的数据库实现(比如说刚开始我们用的是mysql数据库,等我们上线了后,客户提出换成oracle数据库,如果我们是直接用sql写的,那我们就需要改成百上千的代码,而且还不知道哪些需要改) ,如果用的是model,我们只需要在settings里面改一下数据库驱动和连接就行了。

MySQL知识补充
truncate table 表名; — 清空表数据(消除自增)
delete from 表名; — 清空表数据(不消除自增)
show create table 表名; – 查看表结构的真实情况

小TIPS
如果你放进去的是垃圾,那么你后面肯定就会拿到垃圾
弱水三千,只取一瓢

https://blog.csdn.net/sinat_37064286/article/details/82224562

win创建虚拟环境,在当前目录下:python -m venv venv
激活:venv/Scripts/activate
linux激活:source venv/Scripts/activate

你有没有遇到过数据量很大,查询很慢的时候?

遇到过,我们之前有一个项目他的数据量达到了千万级,刚开始数据量小的时候,性能还不错,能在3秒之内。后面上线了半年之后,我们数据量上去了,查询就变得很慢,比如说1分钟才出来

你是如何解决的呢?
我给相应的字段添加了索引,变快了!
是真的吗?如何加的?
直接加的!
你可以走了

解释:
当数据量很大的时候,而且系统还在运行时,你加索引会卡死,会造成一定的数据库表死锁,然后系统就会报500错误。

解决方法:
先创建一张空表和原来的那个表结构一模一样,在创建表的时候就把这张新表的索引建立好
再把旧表里面的数据导入到新表里面
把旧表集名字改成另外的名字,把新表名改称旧表的名字
索引就加上了

安装Selenium

1.查看chrome版本

image.png

2.下载驱动
http://npm.taobao.org/mirrors/chromedriver
下载对应版本,之后解压

image.png

在D:根目录建立一个tools空目录
将chromedriver.exe复制进去

image.png

然后添加环境变量

image.png

直接在系统变量中的path编辑新建,然后移到首位!

在cmd中打出:chromedriver.exe

image.png

如果看到以下图片说明成功

image.png

最后直接终端输入:pip install selenium

隐式等待和显示等待得区别

显示等待 – 就是你指定等待多久,他就会固定等待那么久,即使元素加载完毕他也会等到固定得时间才完成,比如说time.sleep(5)这就是显示等待

隐式等待 – 就是你指定一个等待时间,如果在这个等待时间之内完成,那就不用一直等到固定的时间结束,会马上完成。如果过了这个指定的等待时间都没完成,会抛出异常

代理IP

可以使用的范围:爬虫采集,秒杀抢购,营销补量(刷流量),投票助力

urllib,requests,selenium 都可以设置IP
IP代理池主要包含哪些模块
爬取模块 – 负责从提供代理IP的网站把ip和端口收集回来,有很多网站(比如西刺)
存储模块 – 我们采用redis作为代理ip保存的数据库,我们使用redis里面的有序集合来保存
测试模块 – 用我们要爬取的目标网址去定时测试数据库里面的ip是否有效,并进行打分或者去除
接口模块 – 通过读取数据库里面的高分的代理IP,提供给使用方(我们使用的是flask或者Django)

你是计算机专业的?
学过操作系统没有?
知道操作系统主要负责那些东西?
磁盘管理,进程管理,内存管理,输入输出管理

上周回顾
爬虫得大体介绍(爬虫能做什么,如何学)
http协议的介绍(请求头,请求体,响应头,响应体,http method, http status code )
获取网页的方法(urllib,requests,seleinum)
解析网页得方法(re,xpath-lxml,css-bs4)
获取动态网页的方法(ajax请求,selenium)
保存解析后得内容(pymysql,sqlalchemy)
用selenium实现网页操作自动化(get element,click, input, drag and drop,iframe,action chains…)
验证码,图形验证码(超级鹰),滑块验证码(极验验证码),短信验证码,旋转验证码
ip代理池 企查猫故意让他屏蔽ip,使用了付费代理ip(蘑菇代理)

爬虫 scrapy框架

特点:爬取效率高,扩展性强,python编写跨平台运行
数据流程

image.png
image.png

mongodb数据库

NoSQL,全称是”Not Only Sql”,指的是非关系型的数据库。
非关系型数据库主要有这些特点:非关系型的、分布式的、开源的、水平可扩展的。

水平扩展和垂直扩展的区别
水平扩展:通过增加服务器数量的方式来进行扩展,对每台服务器的要求不高,可以灵活的增加或减少服务器 – – 我们现在一般都使用水平扩展

垂直扩展:通过增加服务器的硬件性能来进行扩展,对服务器的要求很高,不是太灵活然后更换费用贵

NoSQL的拥护者们提倡运用非关系型的数据存储,通常的应用如:模式自由、支持简易复制、简单的AP1、大容量数据等。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
MongoDB最大的特点:它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能。它是一个面向集合的,模式自由的文档型数据库。

1、面向集合(Collection-Orented)……意思是数据被分组存储在数据集中,被称为一个集合(Collection),每个集合在数据库中 都有有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定义任何模式

2、模式自由(schema-free)存储在MongoDB数据库中的文件,我们不需要知道它的任何结构定义。提了这么多次”无模式”或”模式自由”,它到是个什么概念呢?例如,下面两个记录可以存在于同一个集合里面

3、文档型意思是我们存储的数据是键-值对的集合,键是字符串,值可以是数据类型集合里的任意类型,包括数组和文档,文件存储格式为BSON(一种JSON的扩展)

适用场景
1网站数据:MongoDB非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性
2缓存:由于性能很高,MongoDB也适合作为信息基础设施的缓存层。在系统重启之后,由MongoDB搭建的持久化缓存层可以避免下层的数据源过载
3大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储
4高伸缩性的场景:MongoDB非常适合由数十或数百台服务器组成的数据库。MongoDB的路线图中已经包对MapReduce引擎的内置支持
5用于对象及JSON数据的存储:MongoDB的BSON数据格式非常适合文档化格式的存储及查询

服务端命令
bin/mongod.exe
客户端命令
mongo.exe

MongoDB简单使用

use 数据库名字 – 建立数据库(没有就建立,有就切换)
如果是新建,这个时候还没有数据,但我们给数据库中的集合插入文档的时候自动创建文档,集合,数据库

db.集合名.insert({‘字段1′:值1,’字段2’:值2,……}) – 插入数据,值也可以是字典集合任何东西

db.集合.find() – 查询集合内所有数据
db.集合.find({‘name’:’张三’}) – 查询集合内’name’叫’张三’的数据

db.集合.remove({‘name’:’张三’}) – 删除叫’张三’的数据
db.集合.remove({}) – 清空集合
de.集合.drop() – 删除集合
db.dropDatabase() – 删库

db.集合.update({‘原字段’:’原值’},{‘新字段’:’新值’}) – 第一个文档为查询文档,第二个为更新后的文档,更新之后的文档会将整个之前的原文档全部覆盖

使用修改器inc更新 - 原子操作 db.集合.update(查询文档,{'inc’:{‘price’:100}}) – 给集合中对应的文档中price增加100

image.png

image.png

安装scrapy
windows
第一步
检查自己的python版本

image.png

虚拟环境和全局不一样,根据自己的需求选择

第二步
打开网址
https://www.lfd.uci.edu/~gohlke/pythonlibs/

下载Twisted
找到跟自己python对应的版本

image.png

下载完成后放到一个比较容易找到的目录,路劲中不要出现中文
然后安装:
打开你的终端,虚拟环境或全局根据刚才查询Python版本一样
pip install D:toolsTwisted-19.10.0-cp37-cp37m-win_amd64.whl

image.png

后面跟你的刚才文件的绝对路径
如果怕把路径打错可以直接cd到对应的目录下直接跟文件名
第三步
接下来继续安装pypiwin32和scrapy
pip install pypiwin32
pip install scrapy

image.png

如果都成功了就大功告成!
使用Scrapy
以下命令都直接再终端输入,不要运行程序
项目结构

image.png
image.png

scrapy crawl 蜘蛛 -o result.json

前端补充:
取a标签里面的href属性
attr是连接,text是文字
response.selector.css(‘.author.box a::类型(属性)’).extract()
response.selector.css(‘.author.box a::attr(href)’).extract()

linux补充
cp -av 地址 – 拷贝有内容的目录

scp 文件名 root@主机ip:/root/目录 – 在本地远程给linux传输文件

python3 -m venv venv – 建立虚拟环境
source venv/bin/activate – 激活虚拟环境
deactivate – 退出虚拟环境

网页简单部署上线!

1.在本地先建立自己的django项目保证能正常运行!
2.在阿里云建立相同的工程目录
3.在工程目录下建立虚拟环境
python3 -m venv venv
看见venv文件夹之后激活虚拟环境
source venv/bin/activate
4.到本地的django工程生成最新的依赖项清单
终端输入
pip freeze > requirements.txt
5.然后找到这个requirements.txt的目录下,打开终端
将文件传送到服务器
scp 文件名 root@主机ip:/root/目录
拷过去之后pip install -r requirements.txt

image.png

全部搞定之后到本地打包整个django项目文件
直接打包成zip格式最好
然后一样上传到服务器刚才那个目录下
然后解压
unzip 文件名
如果没有安装unzip直接安装:yum install unzip 就行
解压成功之后可以直接python manage.py runserver 0.0.0.0:8080
如果没报错就可以直接访问了
前提必须要把相应的端口打开
确认没问题之后将setting的debug改为False
下面这条代码可以后台运行
nohup python manage.py runserver 0.0.0.0:8080 &
注意:
如果改为debug false之后无法加载静态资源可以
manage.py runserver –insecure
这条命令可以在不安全的模式下运行
上传到服务器记得将数据库配置跟服务器一致,
如果使用pymysql启动数据库那django的版本不能太高2.1.4就行
如果要使用高版本的django就只能用mysql-client
关闭项目直接杀死进程就行
kill -9 进程号

scrapy本身是不带分布式功能的,装了插件之后才实现了分布式功能
Scrapy去重原理(考点)
在你构建请求的时候,传一个dont_filter=False参数可以开启去重,默认是没有开启的,
在scrapy源码中可以找到一个dupefilters.py去重器;这个里面有去重的逻辑。它会把传过去的request对象通过hash摘要的形式生成16进制的hash码。然后判断是否重复就是比较这个hash码,存放request指纹是使用python的set类型。

梅开二度(骆神版)

获取网页源代码
resp = requests.get(url)
resp.content / resp.text

解析网页源代码
正则表达式解析-re
xpath解析 – lxml
css选择器 – pyquery / beautifulsoup
document.querySelector(‘#hello’)
document.querySelectorAll(‘.good’)

比较靠谱的商业ip代理

image.png

列表双关队列
deque,跟列表差不多,效率极高
from collection import deque
a =deque()

添加 – append
左边添加 – appendleft
最后删除 – pop
左边删除 – popleft

自动拼接url
urljoin(url1,url2)
自动解析url
urlparse(url)

代理池
cookie池

线程池也可以用上下文语法
迭代器协议,iter,next
上下文协议,enter,exit

多线程爬虫+多线程写入数据
示例:

import hashlib
import io
from collections import deque
from concurrent.futures.thread import ThreadPoolExecutor
from threading import local
from urllib.parse import urljoin, urlparse

import MySQLdb
import bs4
import requests

MAX_DEPTH = 3

visited_urls = set()
new_urls = deque()
seed_url = 'https://sports.sohu.com/'
netloc = urlparse(seed_url).netloc


def _force_bytes(data):
    if type(data) != bytes:
        if type(data) == str:
            data = data.encode()
        elif type(data) == io.BytesIO:
            data = data.getvalue()
        else:
            data = bytes(data)
    return data


def make_md5_digest(data):
    """生成MD5摘要"""
    data = _force_bytes(data)
    return hashlib.md5(data).hexdigest()


def get_db_connection():
    thread_local = local()
    if not hasattr(thread_local, 'conn'):
        conn = MySQLdb.connect(host='主机', port=3306,
                               user='用户名', password='用户密码',
                               database='数据库', charset='编码',
                               autocommit=True)
        setattr(thread_local, 'conn', conn)
    return getattr(thread_local, 'conn')


def write_to_db(url, content, digest):
    with get_db_connection().cursor() as cursor:
        cursor.execute(
            'insert into tb_page (url, content, digest) values (%s, %s, %s)',
            (url, content, digest)
        )


def fix_url(curr_url, href):
    if href.startswith('//'):
        href = f'http:{href}'
    elif href.startswith('/'):
        href = urljoin(curr_url, href)
    elif href.startswith('mailto') or href.startswith('javascript')
            or href.startswith('#') or href.startswith('?'):
        href = ''
    href = href.replace('!''}', '')
    return urljoin('http://', href) if href else href


def fetch_page(curr_url, depth):
    try:
        resp = requests.get(curr_url, timeout=1)
    except Exception as ex:
        print(ex)
    else:
        write_to_db(curr_url, resp.text, make_md5_digest(resp.text))
        soup = bs4.BeautifulSoup(resp.text, 'lxml')
        if depth  0 or (future and not future.done()):
            # print(len(new_urls))
            if len(new_urls) > 0:
                curr_url, depth = new_urls.popleft()
                if curr_url not in visited_urls:
                    visited_urls.add(curr_url)
                    future = pool.submit(fetch_page, curr_url, depth)


if __name__ == '__main__':
    main()

文章来源于互联网:爬虫

发表评论