Python3 爬虫

news/2024/7/19 8:57:58 标签: 爬虫, json, 运维

Python3 开发环境配置

请求库安装

需要用到的Python库来实现HTTP请求操作,如:Requests,Selenium,Aiotttp等。

  1. Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作。
  2. ChromeDriver驱动Chrome浏览器完成相应的操作。 在 chromedriver 所在的路径下执行。

sudo mv chromedriver /usr/bin
注意可能会遇到一下错误:
csrutil: failed to modify system integrity configuration. This tool needs to be executed from the Recovery OS.
遇到这种情况需要先关机,再开机并按住command+r键,进入恢复模式。在终端执行csrutil disable关闭SIP。

如果控制台有类似输出,则表示chromedriver 的环境变量配置好了。

from selenium import webdriver
browser = webdriver.Chrome()
复制代码

运行之后会弹出一个空白的Chrome浏览器。
3. Aiohttp是一个提供异步Web服务的库,Aiohttp的异步操作可以借助async/await关键字。
4. LXML的安装,支持HTML和XML的解析
5. BeautifulSoup的安装,这个库的HTML和XML解析器是依赖于LXML库。
6. pyquery也是一个强大的网页解析工具,它提供了和jQuery类似的语法来解析HTML文档,支持CSS选择器。

安装数据库
1.mysql 轻量级的关系型数据库,以表的形式存储数据
2.MongDB是由C++编写的非关系型数据库,是一个基于分布式文件储存的开源数据系统,其内容存储形式类似Json对象。 3.Redis 是一个基于内存的高效的非关系型数据库

brew install redis
复制代码

启动Redis服务器

brew services start redis
redis-server /usr/local/etc/redis.conf

//关闭和重启
brew services stop redis
brew services restart redis
复制代码

4.PyMySQL python3中想要将数据存储在mysql中需要借助于PyMySQL来操作。

pip3 install pymysql
复制代码

验证安装

>>> import pymysql
>>> pymysql.VERSION
(0, 9, 2, None)
>>>
复制代码

爬虫框架

#####一. PySplider强大的网络爬虫框架

pip3 install pyspider
复制代码

在python3.7上会报如下错:

Traceback (most recent call last):
  File "/usr/local/bin/pyspider", line 6, in <module>
    from pyspider.run import main
  File "/usr/local/lib/python3.7/site-packages/pyspider/run.py", line 231
    async=True, get_object=False, no_input=False):
        ^
SyntaxError: invalid syntax
复制代码

原因是因为async从python3.7开始不能用作参数名了

#####二. Scrapy

pip3 install Scrapy
复制代码

####三. ScrapySplash
ScrapySplash 是一个Scrapy钟支持JavaScript的渲染工具,它的安装分为两个部分,一个是Splash服务的安装,安装方式Docker,安装之后会启动一个splash的服务,可以通过它的接口来实现JavaScript页面的加载。另外一个是ScrapySplsh的Python库安装,安装之后即可在Scrapy中使用Slpash服务。

安装Splash

docker run -p 8050:8050 scrapinghub/splash
复制代码

ScrapySplash的安装

pip3 install scrapy-splash
复制代码

####四. ScrapyRedis的安装

ScrapyRedis是Scrapy分布式的扩展模块,可以实现Scrapy分布式爬虫的搭建。

pip3 install scrapy-redis
复制代码

安装测试

$ python3
>>> import scrapy_redis
复制代码

####五.部署相关库的安装

1.Docker的安装

brew cask install docker
复制代码

2.Scrapyd 的安装

Scraypd是一个用于部署和运行Scrapy项目的工具,可以将Scrapy项目上传到云主机并通过API来控制它的运行。

pip3 install scrapyd
复制代码

3.ScrapydClient的安装

pip3 install scrapyd-client
复制代码

安装成功之后会有一个部署命令,叫做 scrapyd-deploy。

scrapyd-deploy -h
复制代码

4.ScrapydAPI的安装

pip3 install python-scrapyd-api
复制代码

可以直接请求它提供的api即可获取当前主机的Scrapy 任务运行状况。

from scrapyd_api import ScrapydAPI
scrapyd = ScrapydAPI('http://localhost:6800')
print(scrapyd.list_projects())
复制代码

用Python直接获取各个主机Scrapy任务的运行状态了。

5.Scrapyrt提供一个调度的HTTP的接口,有了它我们不需要再执行Scrapy命令而是通过请求一个HTTP接口即可调度Scrapy任务。

pip3 install scrapyrt
复制代码

爬虫基础

使用代理可以成功伪装IP地址,避免本机ip被因为单位时间请求次数过多被封。

代理分类

根据协议分类
FTP代理服务器
HTTP 代理服务器
SSL/TLS代理,主要用于访问加密网站
Telnet代理,主要用于Realplayer访问 Real流媒体服务器,一般有缓存功能,端口一般为554
POP3/SMTP代理 主要用于POP3/SMTP方式收发邮件,一般有缓存功能,端口一般为110/25
SOCKS代理,只是单纯传递数据包,不关心具体的协议和用法,一般端口为1080

基本库的使用

###urllib
使用urllib的request模块发起网络请求

import ssl
import urllib.request
import urllib.parse

ssl._create_default_https_context = ssl._create_unverified_context
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data= data)
print(response.read().decode('utf-8'))
print(type(response))
复制代码

在python3中需要import ssl这库。其中'httpbin.org/post'这地址是可以…

urlopen()只能发起简单的网络请求,如果需要设置更多的Header信息,需要用到Request

页面认证

有一些网页需要输入密码等信息才能查看页面内容,就需要认证,可以借助HTTPBasicAuthHandler完成认证

from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from  urllib.error import URLError

username = 'username'
password = 'password'

url = 'http://127.0.0.1:8000/06.%E7%9B%92%E5%AD%90%E6%A8%A1%E5%9E%8B.html'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)

try:
    result = opener.open(url)
    html = result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)
复制代码

分析robotparser协议

Robots协议也被称作爬虫协议、机器人协议,用来告诉爬虫和搜索引擎哪些页面可以抓取,它通常有一个robots.txt的文本文件,放在网站的根目录下。
robots.txt的样例:

User-agent: * 
Disallow: /
Allow: /public/
复制代码

User-agent描述了搜索爬虫的名称;Disallow指定了不允许抓取的目录,上面的例子表示不允许抓取所有页面;Allow则是表示可以抓取的页面。

RobotParser模块提供了一个RobotFileParser,它可以根据某网站的robots.txt文件来判断一个爬取爬虫是否有权限来爬取这个网页。

抓取二进制数据

图片是二进制数据,是以bytes类型的数据

import requests

r = requests.get('https://github.com/favicon.ico')
with open('favicon.ico', 'wb') as f:
    f.write(r.content)

复制代码

open()方法第一个参数是文件名,第二个参数是代表以二进制写的形式打开,可以向文件写入二进制数据并保存。

注释:需要搭建一个web框架,这里我直接用python 的框架库搭建了一个本地的服务器,用于本地的网络请求

python开发中有很多好的web框架,如Django、Tornado、Flask

Flask是一个面向简单需求小型应用的微框架。
Django包括一个开箱即用的ORM,全能型的web框架。 Tornado Facebook的开源异步web框架

我用Tornado搭建了一个web框架用于本地的网络请求,写了一个GET 和 POST 请求。

from tornado.web import Application, RequestHandler, url
from tornado.ioloop import IOLoop
from tornado.httpserver import HTTPServer
import tornado.options
import json

tornado.options.define('port', default=9000, type=int, help='this is the port >for application ')

class RegistHandler(RequestHandler):
    """docstring for RegisterHandler"""

    def initialize(self, title):
        self.title = title

    def set_default_headers(self):
        self.set_header("Content-type", "application/json; charset=utf-8")
        self.set_header("js", "zj")

    def set_default_cookie(self):
        self.set_cookie("loginuser", "admin")
        print(self.get_cookie("loginuser"))

    def get(self):
        username = self.get_query_argument('username')
        self.set_default_headers()
        self.set_default_cookie()
        self.write("{'简书':'知几'}")

    def post(self):
        self.set_default_headers()
        # username = self.get_argument('username')

        with open('app.json', 'r') as f:
            data = json.load(f)
            self.write(data)
            
 if __name__ == '__main__':
    # #创建一个应用对象
    # app = tornado.web.Application([(r'/',IndexHandler)])
    # #绑定一个监听端口
    # app.listen(9000)
    # #启动web程序,开始监听端口的连接
    # tornado.ioloop.IOLoop.current().start()

    app = Application(
        [(r'/', IndexHandler),
         (r'/regsit', RegistHandler, {'title': '会员注册'})], debug=True)
    tornado.options.parse_command_line()
    http_server = HTTPServer(app)
    print(IOLoop.current())
    # 最原始的方式
    http_server.bind(tornado.options.options.port)
    http_server.start(1)

    # 启动Ioloop轮循监听
    IOLoop.current().start()

复制代码

会话维持

在Requests中直接使用get()或者post()等方法虽然能模拟网络请求,但是每次的请求相当于是不同的会话,即不同的Session。

import requests

s = requests.Session()
s.get('http://httpbin.org/cookies/set/number/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)
复制代码

利用Session可以做到模拟同一个会话,而且不用担心Cookies的问题。

###XPath解析库的使用

表达式描述
nodename选取此节点的所有子节点
   /       | 从当前节点选取直接子节点
   //      |从当前节点选取所有子孙节点
   .       |选取当前节点
   ..      |选取当前节点的父节点
   @   | 选取属性
复制代码
from lxml import etree
html = etree.parse('/Users/Cathy/Desktop/test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)
复制代码

如下有这个么一个test.html:

<!DOCTYPE html>
<html lang="en">
<body>
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
</body>
</html>
复制代码

获取其父节点,然后获取其class属性

from lxml import etree
html = etree.parse('/Users/Cathy/Desktop/test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)
复制代码

属性匹配
可以用@符号进行属性过滤,这里是选取class为Item-1的li节点。

from lxml import etree
html = etree.parse('/Users/Cathy/Desktop/test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)
复制代码

匹配的结果又两个元素:

[<Element li at 0x109b66dc8>, <Element li at 0x109b66e08>]
复制代码

文本的获取

通过text()方法获取节点中的文本,这里的文本都在a中,如果直接通过li是获取不到文本的。

from lxml import etree
html = etree.parse('/Users/Cathy/Desktop/test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)
复制代码

PyQuery

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>
'''
复制代码

初始化一个pyquery的对象,传入一个css的选择器,#container .list li的意思是选取id为container的节点内部的class为list的节点内部的所有li节点。

from pyquery import PyQuery as pq
doc = pq(html)
li = doc('#container .list li')
print(li)
复制代码

查找节点也可以用find()方法 ,会将符合条件的所有节点选择出来。

from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
print(items.find('li'))
复制代码

还提供了children() 、parent()等方法来获取某个节点的子节点和父节点。

获取信息
提取到节点之后,就是获取节点所包含的信息,如获取属性和文本。

###PyMySQL

mysql连接的时候需要额外指定一个db

import pymysql

#建表
db = pymysql.connect(host='localhost', user='root', password='123456', port=3306, db='spiders')
cursor = db.cursor()
sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY (id))'
cursor.execute(sql)
db.close()

复制代码

向表中插入数据

#插入数据
id = '201200003'
user = 'Wang'
age = 11

sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'
try:
    cursor.execute(sql, (id, user, age))
    db.commit()
    print('ok')
except:
    db.rollback() 
    print('error')
db.close()

复制代码

如果发生异常,则rollback()执行数据回滚

# 查询数据

sql = 'SELECT * FROM students'
try:
    cursor.execute(sql)
    print('count:', cursor.rowcount) #获取数据查询结果的条数
    one = cursor.fetchone() #获取第一条数据,偏移指针会指向下一条数据
    print('one:', one)
    results = cursor.fetchall()
    print('results:', results)
except:
    print('error')
复制代码

Ajax

Ajax就是Asynchronous JavaScript andXML ,即异步JavaScript和XMl。
Ajax 不是一门编程语言,而是利用 JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术

如下面链接m.weibo.cn/ 以getIndex开头的链接中 查看他的Headers,如果请求中会有一个信息为 X-Requested-With:XMLHttpRequest 这就代表着是Ajax.

在响应体里面返回了页面所有的渲染数据,JavaScript接收到这个数据之后,再执行渲染方法.
我们所看到的页面展示的数据并不是最原始的页面返回的,而是通过执行了JavaScript后再次向后台发送了Ajax请求,拿到数据进一步渲染,在Chrome查看网络请求的时候,可以通过XHR筛选出是Ajax的请求

Ajax结果提取
from urllib.parse import urlencode
from pyquery import PyQuery
import requests

base_url = 'https://m.weibo.cn/api/container/getIndex?'
headers = {
'Host': 'm.weibo.cn',
    'Referer': 'https://m.weibo.cn/u/2145291155',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
    'X-Requested-With': 'XMLHttpRequest',
}

def get_page(page):
    params = {
        'type': 'uid',
        'value': '2145291155',
        'containerid': '1076032145291155',
        'page': page
    }

    url = base_url + urlencode(params)
    try:
        response = requests.get(url=url,headers=headers)
        if response.status_code == 200:
            return response.json()
    except requests.ConnectionError as e:
        print('Error', e.args)

def parse_page(json):
    if json:
        items = json.get('data').get('cards')
        print(items)
        for item in items:
            item = item.get('mblog')
            weibo = {}
            weibo['id'] = item.get('id')
            weibo['text'] = PyQuery(item.get('text')).text()
            weibo['attitudes'] = item.get('attitudes_count')
            weibo['comments'] = item.get('comments_count')
            weibo['reposts'] = item.get('reposts_count')

            yield weibo


if __name__ == '__main__':
    json = get_page(1)# 这个是页面数
    results = parse_page(json)
    for result in results:
        print(result)

复制代码

动态渲染页面抓取

####一. Selenium的使用 Selenium 是一个自动化测试工具,利用它我们可以驱动浏览器执行特定的动作,如点击、下拉等等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬

selenium只能在 Chrome version must be between 70 and 73上使用,所以这里我们略过他

####二. Splash的使用 #####安装Splash Splash 是一个 JavaScript 渲染服务,是一个带有 HTTP API 的轻量级浏览器,同时它对接了 Python 中的 Twisted和 QT 库

启动Splash需要安装docker

拉取镜像

sudo docker pull scrapinghub/splash
复制代码

启动容器,监控的端口为8050

docker run -p 8050:8050 scrapinghub/splash

复制代码

打开http://localhost:8050/ 就可以看到Splash的web页面。

Splash上默认的是http://google.com的地址,点击渲染,就能看到网页的返回结果呈现了渲染截图、HAR 加载统计数据、网页的源代码

我们能看到一段脚本:

function main(splash, args)
  assert(splash:go(args.url))
  assert(splash:wait(0.5))
  return {
    html = splash:html(),
    png = splash:png(),
    har = splash:har(),
  }
end
复制代码

这个脚本是Lua语言写的脚本,这段代码的主要作用就是调用go() 方法去加载页面,返回了页面的源码、截图和HAR信息。

下面这段代码可以看到,通过evaljs()方法传入JavaScript的脚本,而document.title的执行结果就是返回网页的标题

function main(splash, args)
  splash:go("http://www.baidu.com")
  splash:wait(0.5)
  local title = splash:evaljs("document.title")
  return {title=title}
end
复制代码

####异步处理 Splash是支持异步处理的

function main(splash, args)
  local example_urls = {"www.taobao.com","www.zhihu.com"}
  local urls = args.urls or example_urls
  local results = {}
  for index, url in ipairs(urls) do
    local ok, reason = splash:go("http://" .. url)
    if ok then
      splash:wait(2)
      results[url] = splash:png()
    end
  end
  return results
end
复制代码

运行的结果是两个网站的截图

Splash的对象属性

main方法的中的第二个参数args相当于是splash.args属性

function main(splash, args)
    local url = args.url
end
复制代码
function main(splash)
    local url = splash.args.url
end
复制代码

#####js_enabled

这个属性是Splash的JavaScript的开关,true或者false来控制是否执行JavaScript代码。

#####resource_timeout

此属性可以设置加载的超时时间,单位是秒,如果设置为 0或 nil(类似 Python 中的 None)就代表不检测超时,我们用一个实例感受一下

function main(splash)
    splash.resource_timeout = 0.1
    assert(splash:go('https://www.taobao.com'))
    return splash:png()
end
复制代码

#####go() go() 方法就是用来请求某个链接的方法,而且它可以模拟 GET 和 POST 请求,同时支持传入 Headers、Form Data 等数据。

#####jsfunc() 此方法可以直接调用 JavaScript 定义的方法,需要用双中括号包围,相当于实现了 JavaScript 方法到 Lua 脚本的转换。

#####evaljs() 可以执行 JavaScript 代码并返回最后一条语句的返回结果

####runjs() 此方法可以执行 JavaScript 代码,和evaljs的功能类似,但此方法更偏向于执行某一些动作或申明某些方法。而evaljs()更偏向获取执行某些结果。

function main(splash, args)
  splash:go("https://www.baidu.com")
  splash:runjs("foo = function() { return 'bar' }")
  local result = splash:evaljs("foo()")
  return result
end
复制代码
autoload()

可以设置在每个页面访问时自动加载的对象

splash:autoload{source_or_url, source=nil, url=nil}
复制代码

参数说明:
1.source_or_url,JavaScript 代码或者 JavaScript 库链接
2.source,JavaScript 代码。
3.url,JavaScript 库链接

此方法只是负责加载JavaScript代码或库,不执行任何操作,如果要执行操作可以调用evaljs() 或者runjs()方法

function main(splash, args)
  splash:autoload([[
    function get_document_title(){
      return document.title;
    }
  ]])
  splash:go("https://www.baidu.com")
  return splash:evaljs("get_document_title()")
end
复制代码

####Splash API的调用 让Splash和Python程序结合使用并抓取JavaScript渲染的页面,Splash提供了一些HTTP接口,只需要请求这些接口并传递响应的参数即可获取页面渲染后的结果。

curl http://localhost:8050/render.html?url=https://www.baidu.com
复制代码

用Python实现的话如下面代码:

import requests
url = 'http://localhost:8050/render.html?url=https://www.baidu.com'
response = requests.get(url)
print(response.text)
复制代码

转载于:https://juejin.im/post/5c8df55051882545c36ddea1


http://www.niftyadmin.cn/n/968855.html

相关文章

codeforces 886D. Restoration of string (字符串处理+类拓扑排序)

搞了好久的题&#xff0c;现在看到这个题有点恶心&#xff0c;先让我缓缓…… 传送门&#xff1a;codeforces 886D 题目大意&#xff1a; 给出 n 个字符串&#xff0c;求一个结果串&#xff0c;使得这 n 个字符串都是结果串中出现频率最高的子串。如果存在则输出字典序最小的…

04----mockjs的数据类型 及 语法规范

1.Mock.Random Mock.Random是一个工具类&#xff0c;用于生成各种随机数据。Mock.Random 的方法在数据模板中称为『占位符』&#xff0c;书写格式为 占位符(参数 [, 参数]) 2.Mock.Random 提供的完整方法&#xff08;占位符&#xff09;如下 TypeMethodBasicboolean, natural,…

设计模式|单例模式、单例模式的懒汉式为什么是线程不安全的、懒汉式如何实现线程安全

目录 单例模式介绍 懒汉式为什么是线程不安全的 懒汉模式实现线程安全 1.对实例化方法加锁 2.double checkvolatile方案 单例模式介绍 说明&#xff1a;保证一个类只有一个实例&#xff0c;并提供一个全局访问点 场景&#xff1a;数据库连接池的设计、优点&#xff1a;在内…

分分钟解决MySQL查询速度慢与性能差

阅读本文大概需要 6 分钟。一、什么影响了数据库查询速度1.1 影响数据库查询速度的四个因素1.2 风险分析QPS&#xff1a; QueriesPerSecond意思是“每秒查询率”&#xff0c;是一台服务器每秒能够相应的查询次数&#xff0c;是对一个特定的查询服务器在规定时间内所处理流量多少…

蓝桥杯 K好数 算法训练 (动态规划DP)

传送门&#xff1a;K好数 题目大意&#xff1a; 如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字&#xff0c;那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K 4&#xff0c;L 2的时候&#xff0c;所有K好数为11、13、20、22、30、31、33 共7…

PiFlow v0.5 发布:大数据流水线系统

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; PiFlow是一个基于分布式计算框架Spark开发的大数据流水线系统。该系统将数据的采集、清洗、计算、存储等各个环节封装成组件&#xff0c;以所见即所得方式进行流水线配置。简单易用&#xff0c;…

51nod 1478 括号序列的最长合法子段 (括号匹配)

传送门&#xff1a;51nod 1478 思路&#xff1a; 一般与括号匹配有关的问题基本套路就是先直接来一遍括号匹配&#xff0c;然后再处理一下。 一开始我想一遍匹配一遍确定答案&#xff0c;但是这样有个问题&#xff1a;无法确定长度值什么时候归零。换句话说&#xff0c;对于当…

hashSet 是有序的吗、怎么保证唯一性

目录 hashSet 是有序的吗 怎么保证唯一性 hashSet 是有序的吗 这个问题是在去年面试的时候第一次被问到&#xff0c;听到问题的时候一下懵了&#xff0c;因为工作中都是用hashSet保证数据的唯一&#xff0c;没有考虑过数据的顺序性&#xff0c;也没有私下研究过hashSet的源码…