提交 60587f02 编写于 作者: J jackfrued

更新了部分文档和代码

上级 3ef37219
......@@ -32,7 +32,7 @@
| \| | 分支 | foo\|bar | 可以匹配foo或者bar |
| (?#) | 注释 | | |
| (exp) | 匹配exp并捕获到自动命名的组中 | | |
| (? <name>exp) | 匹配exp并捕获到名为name的组中 | | |
| (?<name>exp) | 匹配exp并捕获到名为name的组中 | | |
| (?:exp) | 匹配exp但是不捕获匹配的文本 | | |
| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc |
| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing |
......
......@@ -68,7 +68,7 @@
</li>
</ul>
<div>
<input @keydown.enter="addItem()" type="text" id="fname" v-model="fname">
<input @keydown.enter="addItem()" type="text" id="fname" v-model.trim="fname">
<button id="ok" @click="addItem()">确定</button>
</div>
</div>
......@@ -82,8 +82,8 @@
},
methods: {
addItem() {
if (this.fname.trim().length > 0) {
this.fruits.push(this.fname.trim())
if (this.fname.length > 0) {
this.fruits.push(this.fname)
}
this.fname = ''
},
......
......@@ -65,7 +65,7 @@
<div class="search">
<!-- type属性是text的input标签代表文本框 可以接收用户输入 -->
<!-- placeholder是文本框的输入提示 -->
<input type="text" placeholder="请输入垃圾名字" v-model="word" @keydown.enter="search()">
<input type="text" placeholder="请输入垃圾名字" v-model.trim="word" @keydown.enter="search()">
<!-- button代表按钮 点击可以开始查询 -->
<button @click="search()">查询</button>
</div>
......@@ -75,9 +75,9 @@
<div v-for="result in results">
<p>
<!-- img是图像标签 可以用来实现图片-->
<img :src="pictures[result.type]" width="56" :alt="types[result.type]">
<img :src="'images/' + pictures[result.type]" width="56" :alt="types[result.type]">
&nbsp;&nbsp;
<!-- span是跨度标签 代表一个逻辑区域(不分段)-->
<!-- span是跨度标签 代表一个逻辑区域-->
<span>{{ result.name }}</span>
&nbsp;&nbsp;
<span class="pre" v-if="result.aipre == 1">(预测结果)</span>
......@@ -102,7 +102,7 @@
// 查询垃圾分类的函数
search() {
if (this.word.trim().length > 0) {
let key = '9aeb28ee8858a167c1755f856f830e22'
let key = '9636cec76ee2593ba6b195e5b770b394'
let url = `http://api.tianapi.com/txapi/lajifenlei/?key=${key}&word=${this.word}`
fetch(url)
.then(resp => resp.json())
......
......@@ -74,6 +74,8 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行
```Shell
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
```
......
drop database if exists hrs;
create database hrs default charset utf8;
create database hrs default charset utf8mb4;
use hrs;
......@@ -14,6 +14,8 @@ dloc varchar(20) not null comment '所在地',
primary key (dno)
);
-- alter table tb_dept add constraint pk_dept_dno primary key(dno);
insert into tb_dept values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
......@@ -29,11 +31,13 @@ mgr int comment '主管编号',
sal int not null comment '员工月薪',
comm int comment '每月补贴',
dno int comment '所在部门编号',
primary key (eno)
primary key (eno),
foreign key (dno) references tb_dept(dno),
foreign key (mgr) references tb_emp(eno)
);
alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
-- alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
-- alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
insert into tb_emp values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
......@@ -70,4 +74,4 @@ insert into tb_emp values
-- 查询主管的姓名和职位
-- 查询月薪排名4~6名的员工姓名和月薪
-- 查询月薪排名4~6名的员工排名、姓名和月薪
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -54,7 +54,7 @@ class Teacher(models.Model):
> 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。
完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类。
完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类,可以通过修改``
```SQL
from django.contrib import admin
......
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class CartConfig(AppConfig):
name = 'cart'
class FirstConfig(AppConfig):
name = 'first'
from django.db import models
# Create your models here.
from random import sample
from django.shortcuts import render
def show_index(request):
fruits = [
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
]
return render(request, 'index.html', {'fruits': sample(fruits, 3)})
"""
Django settings for shop project.
Django settings for hellodjango project.
Generated by 'django-admin startproject' using Django 2.0.5.
Generated by 'django-admin startproject' using Django 2.2.13.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
......@@ -17,10 +17,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '+gqc54!5+uhvc^o0)fjvihmg&5uu^u+#s5m*fc+e+@bw*(+!w*'
SECRET_KEY = 'x)q$(0m0^ttqii@^zn^9bdbh&%l$)wzjm=nv&_y+^y9e!37=-z'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
......@@ -37,7 +37,6 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cart',
]
MIDDLEWARE = [
......@@ -50,13 +49,12 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'shop.urls'
ROOT_URLCONF = 'hellodjango.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
......@@ -69,26 +67,22 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'shop.wsgi.application'
WSGI_APPLICATION = 'hellodjango.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'Shop',
'HOST': 'localhost',
'PORT': 3306,
'USER': 'yourname',
'PASSWORD': 'yourpass',
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
......@@ -107,11 +101,11 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Chongqing'
TIME_ZONE = 'UTC'
USE_I18N = True
......@@ -121,6 +115,6 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
"""shop URL Configuration
"""hellodjango URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
......@@ -16,11 +16,9 @@ Including another URLconf
from django.contrib import admin
from django.urls import path
from cart import views
from first.views import show_index
urlpatterns = [
path('', views.index),
path('show_cart', views.show_cart),
path('add_to_cart/<int:no>', views.add_to_cart),
path('admin/', admin.site.urls),
path('hello/', show_index),
]
"""
WSGI config for shop project.
WSGI config for hellodjango project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hellodjango.settings')
application = get_wsgi_application()
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hellodjango.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
......@@ -13,3 +15,7 @@ if __name__ == "__main__":
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style>
#fruits {
font-size: 1.25em;
}
</style>
</head>
<body>
<h1>今天推荐的水果是:</h1>
<hr>
<ul id="fruits">
{% for fruit in fruits %}
<li>{{ fruit }}</li>
{% endfor %}
</ul>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
#container {
width: 400px;
margin: 10px auto;
}
.input, form+a {
margin: 20px 20px;
width: 250px;
}
.input>label {
display: inline-block;
width: 70px;
text-align: right;
}
.input:last-child {
text-align: center;
}
input[type=text], input[type=password] {
outline: none;
}
input[type=submit], input[type=reset] {
width: 60px;
}
form+a {
text-decoration: none;
color: blue;
}
.captcha {
width: 380px;
}
.captcha>img {
vertical-align: middle;
}
.hint {
margin-left: 20px;
color: red;
font-size: 0.8em;
height: 20px;
}
</style>
</head>
<body>
<div id="container">
<h1>用户登录</h1>
<hr>
<form action="" method="post">
<div class="input">
<label>用户名:</label>
<input type="text" name="username">
</div>
<div class="input">
<label>密码:</label>
<input type="password" name="password">
</div>
<div class="input captcha">
<label>验证码:</label>
<input type="text" name="captcha">
<img id="code" src="images/captcha.jpg" width="120">
</div>
<div class="input">
<input type="submit" value="登录">
<input type="reset" value="重置">
</div>
</form>
<a href="register.html">注册新用户</a>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
#container {
width: 400px;
margin: 0 auto;
}
.input, form+a {
margin: 20px 20px;
width: 250px;
}
.input>label {
display: inline-block;
width: 80px;
text-align: right;
}
.input:last-child {
text-align: center;
}
input[type=text], input[type=password] {
outline: none;
}
input[type=submit], input[type=reset] {
width: 60px;
}
form+a {
text-decoration: none;
color: blue;
}
.hint {
margin-left: 20px;
color: red;
font-size: 0.8em;
height: 20px;
}
.mobile {
width: 300px;
}
.photo {
width: 350px;
}
.preview {
margin: 10px 50px;
}
.intro {
width: 720px;
}
</style>
</head>
<body>
<div id="container">
<h1>用户注册</h1>
<hr>
<form action="" method="post">
<div class="input">
<label>用户名:</label>
<input type="text" name="username" required minlength="4" maxlength="20">
</div>
<div class="input">
<label>密码:</label>
<input type="password" name="password">
</div>
<div class="input">
<label>确认密码:</label>
<input type="password" name="repassword">
</div>
<div class="input mobile">
<label>手机号:</label>
<input type="tel" name="tel">
<input type="button" id="sendBtn" value="发送验证码">
</div>
<div class="input">
<label>验证码:</label>
<input type="text" name="mobilecode">
</div>
<div class="input">
<input type="submit" value="注册">
<input type="reset" value="重置">
</div>
</form>
<a href="login.html">返回登录</a>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学科信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
#main dt {
font-size: 1.5em;
}
#main dd {
font-size: 1.2em;
}
#main a {
text-decoration: none;
color: blue;
}
</style>
</head>
<body>
<div id="container">
<h1>千锋教育成都校区所有学科</h1>
<hr>
<div id="main">
<dl>
<dt>
<a href="teachers.html">Python全栈+人工智能</a>
<img src="images/hot-icon-small.png">
</dt>
<dd>
Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型、高级编程、通用型编程语言,
由吉多·范罗苏姆创造,第一版发布于1991年。可以视之为一种改良(加入一些其他编程语言的优点,如面向对象)的LISP。
Python的设计哲学强调代码的可读性和简洁的语法(尤其是使用空格缩进划分代码块,而非使用大括号或者关键词)。
相比于C++或Java,Python让开发者能够用更少的代码表达想法。不管是小型还是大型程序,该语言都试图让程序的结构清晰明了。
</dd>
</dl>
<dl>
<dt>
<a href="">全栈软件测试</a>
</dt>
<dd>
软件测试在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
软件测试有许多方法,但对复杂的产品运行有效测试不仅仅是研究过程,更是创造并严格遵守某些呆板步骤的大事。
测试的其中一个定义:为了评估而质疑产品的过程;这里的“质疑”是测试员试着对产品做的事,而产品以测试者脚本行为反应作为回答。
</dd>
</dl>
<dl>
<dt>
<a href="">JavaEE+分布式开发</a>
<img src="images/hot-icon-small.png">
</dt>
<dd>
Java是一种广泛使用的计算机编程语言,拥有跨平台、面向对象、泛型编程的特性,广泛应用于企业级Web应用开发和移动应用开发。
该语言由当时任职于太阳微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形,最初被命名为Oak,
目标设置在家用电器等小型系统的编程语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。
由于这些智能化家电的市场需求没有预期的高,太阳计算机系统(Sun公司)放弃了该项计划。
随着1990年代互联网的发展,Sun公司看见Oak在互联网上应用的前景,于是改造了Oak,于1995年5月以Java的名称正式发布。
Java伴随着互联网的迅猛发展而发展,逐渐成为重要的网络编程语言。
</dd>
</dl>
</div>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>老师信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.teacher {
width: 100%;
margin: 0 auto;
padding: 10px 0;
border-bottom: 1px dashed gray;
overflow: auto;
}
.teacher div {
float: left;
}
.photo {
height: 140px;
border-radius: 75px;
overflow: hidden;
margin-left: 20px;
}
.info {
width: 75%;
margin-left: 30px;
}
.info div {
clear: both;
margin: 5px 10px;
}
.info span {
margin-right: 25px;
}
.info a {
text-decoration: none;
color: blue;
}
.user {
float: right;
margin: 10px 10px;
}
.user+h1 {
clear: both;
}
#photo {
border-radius: 32px 32px 32px 32px;
}
</style>
</head>
<body>
<div id="container">
<h1>Python全栈+人工智能学科的老师信息</h1>
<hr>
<div class="teacher">
<div class="photo">
<img src="images/luohao.png" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:骆昊</strong></span>
<span>性别:男</span>
<span>出生日期:1980年11月28日</span>
</div>
<div class="intro">
10年以上软硬件产品和系统设计、研发、架构和管理经验,2003年毕业于四川大学,四川大学Java技术俱乐部创始人,
四川省优秀大学毕业生,在四川省网络通信技术重点实验室工作期间,参与了2项国家自然科学基金项目、
1项中国科学院中长期研究项目和多项四川省科技攻关项目,在国际会议和国内顶级期刊上发表多篇论文(1篇被SCI收录,3篇被EI收录),
大规模网络性能测量系统DMC-TS的设计者和开发者,perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家,
在Github上参与和维护了多个高质量开源项目,精通C/C++、Java、Python、R、Swift、JavaScript等编程语言,
擅长OOAD、系统架构、算法设计、协议分析和网络测量,主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发,
一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。
</div>
<div class="comment">
<a href="">好评</a>&nbsp;&nbsp;(<strong>100</strong>)
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="">差评</a>&nbsp;&nbsp;(<strong>50</strong>)
</div>
</div>
</div>
<div class="teacher">
<div class="photo">
<img src="images/yuting.png" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:余婷</strong></span>
<span>性别:女</span>
<span>出生日期:1992年9月20日</span>
</div>
<div class="intro">
5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人,
参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,
以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言,
熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微,
性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。
</div>
<div class="comment">
<a href="">好评</a>&nbsp;&nbsp;(<strong>50</strong>)
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="">差评</a>&nbsp;&nbsp;(<strong>100</strong>)
</div>
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
from django.contrib import admin
from cart.models import Goods
class GoodsAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price', 'image')
search_fields = ('name', )
admin.site.register(Goods, GoodsAdmin)
from django.apps import AppConfig
class CartConfig(AppConfig):
name = 'cart'
# Generated by Django 2.0.5 on 2018-05-25 06:28
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Goods',
fields=[
('id', models.AutoField(db_column='gid', primary_key=True, serialize=False)),
('name', models.CharField(db_column='gname', max_length=50)),
('price', models.DecimalField(db_column='gprice', decimal_places=2, max_digits=10)),
('image', models.CharField(db_column='gimage', max_length=255)),
],
options={
'db_table': 'tb_goods',
'ordering': ('id',),
},
),
]
from django.db import models
class Goods(models.Model):
"""商品模型类"""
id = models.AutoField(primary_key=True, db_column='gid')
name = models.CharField(max_length=50, db_column='gname')
price = models.DecimalField(max_digits=10, decimal_places=2, db_column='gprice')
image = models.CharField(max_length=255, db_column='gimage')
class Meta:
db_table = 'tb_goods'
ordering = ('id', )
from django.core import serializers
from django.shortcuts import render, redirect
from cart.models import Goods
def index(request):
goods_list = list(Goods.objects.all())
return render(request, 'goods.html', {'goods_list': goods_list})
class CartItem(object):
"""购物车中的商品项"""
def __init__(self, goods, amount=1):
self.goods = goods
self.amount = amount
@property
def total(self):
return self.goods.price * self.amount
class ShoppingCart(object):
"""购物车"""
def __init__(self):
self.items = {}
self.index = 0
def add_item(self, item):
if item.goods.id in self.items:
self.items[item.goods.id].amount += item.amount
else:
self.items[item.goods.id] = item
def remove_item(self, id):
if id in self.items:
self.items.remove(id)
def clear_all_items(self):
self.items.clear()
@property
def cart_items(self):
return self.items.values()
@property
def total(self):
val = 0
for item in self.items.values():
val += item.total
return val
def add_to_cart(request, id):
goods = Goods.objects.get(pk=id)
# 通过request对象的session属性可以获取到session
# session相当于是服务器端用来保存用户数据的一个字典
# session利用了Cookie保存sessionid
# 通过sessionid就可以获取与某个用户对应的会话(也就是用户数据)
# 如果在浏览器中清除了Cookie那么也就清除了sessionid
# 再次访问服务器时服务器会重新分配新的sessionid这也就意味着之前的用户数据无法找回
# 默认情况下Django的session被设定为持久会话而非浏览器续存期会话
# 通过SESSION_EXPIRE_AT_BROWSER_CLOSE和SESSION_COOKIE_AGE参数可以修改默认设定
# Django中的session是进行了持久化处理的因此需要设定session的序列化方式
# 1.6版开始Django默认的session序列化器是JsonSerializer
# 可以通过SESSION_SERIALIZER来设定其他的序列化器(例如PickleSerializer)
cart = request.session.get('cart', ShoppingCart())
cart.add_item(CartItem(goods))
request.session['cart'] = cart
return redirect('/')
def show_cart(request):
cart = serializers.deserialize(request.session.get('cart'))
return render(request, 'cart.html', {'cart': cart})
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
import pymysql
pymysql.install_as_MySQLdb()
"""
Django settings for shop project.
Generated by 'django-admin startproject' using Django 2.0.5.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '3(n^av%_kt*^2zhz0!iwkxv6_wp^ed7-dpow*vqr7ck0_6=9^e'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cart.apps.CartConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'shop.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'shop.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'shop',
'HOST': 'localhost',
'PORT': 3306,
'USER': 'yourname',
'PASSWORD': 'yourpass',
}
}
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Chongqing'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
STATIC_URL = '/static/'
"""shop URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from cart import views
urlpatterns = [
path('', views.index),
path('add_to_cart/<int:id>', views.add_to_cart),
path('show_cart', views.show_cart),
path('admin/', admin.site.urls),
]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
th, td { margin: 0; padding: 0; width: 180px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; width: 280px; }
.price { color: red; font-size: 18px; }
a { display: inline-block; text-align: center; background-color: red; }
.back { width: 120px; height: 30px; line-height: 30px; }
.del { width: 60px; height: 20px; line-height: 20px; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
.total { text-align: right; }
</style>
</head>
<body>
<div class="left">
<h1>购物车列表</h1>
<hr>
</div>
<div class="right">
<a href="/" class="back">返回</a>
</div>
{% if cart %}
<table style="clear: both;">
<tr>
<th>商品名称</th>
<th>商品单价</th>
<th>商品数量</th>
<th>商品总价</th>
<th>操作</th>
</tr>
{% for item in cart %}
<tr>
<td class="name">{{ item.goods.name }}</td>
<td class="price">&yen;{{ item.goods.price }}</td>
<td>{{ item.amount }}</td>
<td class="price">&yen;{{ item.total }}</td>
<td>
<a href="" class="del">删除</a>
</td>
</tr>
{% endfor %}
<tr>
<td colspan="5" class="total price">&yen;{{ cart.total }}元</td>
</tr>
</table>
<a href="" class="back">清空购物车</a>
{% else %}
<h3 style="clear: both;">购物车中暂时没有商品!</h3>
{% endif %}
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
{% load staticfiles %}
<html>
<head>
<meta charset="utf-8">
<style>
img { display: inline-block; width: 150px; height: 150px; border: 1px solid gray; }
th, td { margin: 0; padding: 0; width: 250px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; }
.price { color: red; font-size: 18px; }
a { display: inline-block; width: 120px; height: 30px; line-height: 30px; text-align: center; background-color: red; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
</style>
</head>
<body>
<div class="left">
<h1>商品列表</h1>
<hr>
</div>
<div class="right">
<a href="/show_cart">查看购物车</a>
</div>
<table style="clear:both;">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>商品图片</th>
<th>操作</th>
</tr>
{% for goods in goods_list %}
<tr>
<td class="name">{{ goods.name }}</td>
<td class="price">&yen;{{ goods.price }}</td>
<td>
<img src="{% static goods.image %}" alt="{{ goods.name }}">
</td>
<td>
<a href="/add_to_cart/{{ goods.id }}">加入购物车</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
\ No newline at end of file
from django.contrib import admin
from cart.models import Goods
class GoodsAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price', 'image')
admin.site.register(Goods, GoodsAdmin)
# Generated by Django 2.0.5 on 2018-05-25 05:11
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Goods',
fields=[
('id', models.AutoField(db_column='gid', primary_key=True, serialize=False)),
('name', models.CharField(db_column='gname', max_length=50)),
('price', models.DecimalField(db_column='gprice', decimal_places=2, max_digits=10)),
('image', models.CharField(db_column='gimage', max_length=255)),
],
options={
'db_table': 'tb_goods',
'ordering': ('id',),
},
),
]
from django.db import models
class Goods(models.Model):
id = models.AutoField(primary_key=True, db_column='gid')
name = models.CharField(max_length=50, db_column='gname')
price = models.DecimalField(max_digits=10, decimal_places=2, db_column='gprice')
image = models.CharField(max_length=255, db_column='gimage')
class Meta:
db_table = 'tb_goods'
ordering = ('id',)
from django.test import TestCase
# Create your tests here.
from django.shortcuts import render
from cart.models import Goods
def index(request):
goods_list = list(Goods.objects.all())
return render(request, 'goods.html', {'goods_list': goods_list})
def show_cart(request):
return render(request, 'cart.html')
def add_to_cart(request, no):
pass
import pymysql
pymysql.install_as_MySQLdb()
\ No newline at end of file
"""
WSGI config for shop project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
application = get_wsgi_application()
insert into tb_goods values
(default, '乐事(Lay’s)无限薯片', 8.2, 'images/lay.jpg'),
(default, '旺旺 仙贝 加量装 540g', 18.5, 'images/wang.jpg'),
(default, '多儿比(Dolbee)黄桃水果罐头', 6.8, 'images/dolbee.jpg'),
(default, '王致和 精制料酒 500ml', 7.9, 'images/wine.jpg'),
(default, '陈克明 面条 鸡蛋龙须挂面', 1.0, 'images/noodle.jpg'),
(default, '鲁花 菜籽油 4L', 69.9, 'images/oil.jpg');
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
th, td { margin: 0; padding: 0; width: 180px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; width: 280px; }
.price { color: red; font-size: 18px; }
a { display: inline-block; text-align: center; background-color: red; }
.back { width: 120px; height: 30px; line-height: 30px; }
.del { width: 60px; height: 20px; line-height: 20px; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
.total { text-align: right; }
</style>
</head>
<body>
<div class="left">
<h1>购物车列表</h1>
<hr>
</div>
<div class="right">
<a href="list_goods" class="back">返回</a>
</div>
{% if cart_items %}
<table style="clear: both;">
<tr>
<th>商品名称</th>
<th>商品单价</th>
<th>商品数量</th>
<th>商品总价</th>
<th>操作</th>
</tr>
{% for item in cart_items %}
<tr>
<td class="name">{{ item.name }}</td>
<td class="price">&yen;{{ item.unit_price }}</td>
<td>{{ item.amount }}</td>
<td class="price">&yen;{{ item.total_price }}</td>
<td>
<a href="" class="del">删除</a>
</td>
</tr>
{% endfor %}
<tr>
<td colspan="5" class="total price">&yen;{{ cart.total }}元</td>
</tr>
</table>
<a href="clear_cart" class="back">清空购物车</a>
{% else %}
<h3 style="clear: both;">购物车中暂时没有商品!</h3>
{% endif %}
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
{% load staticfiles %}
<html>
<head>
<meta charset="utf-8">
<style>
img { display: inline-block; width: 150px; height: 150px; border: 1px solid gray; }
th, td { margin: 0; padding: 0; width: 250px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; }
.price { color: red; font-size: 18px; }
a { display: inline-block; width: 120px; height: 30px; line-height: 30px; text-align: center; background-color: red; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
</style>
</head>
<body>
<div class="left">
<h1>商品列表</h1>
<hr>
</div>
<div class="right">
<a href="/show_cart">查看购物车</a>
</div>
<table style="clear:both;">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>商品图片</th>
<th>操作</th>
</tr>
{% for goods in goods_list %}
<tr>
<td class="name">{{ goods.name }}</td>
<td class="price">&yen;{{ goods.price }}</td>
<td>
<img src="{% static goods.image %}" alt="{{ goods.name }}">
</td>
<td>
<a href="/add_to_cart/{{ goods.id }}">加入购物车</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
\ No newline at end of file
......@@ -27,7 +27,6 @@
大多数网站都会定义robots.txt文件,下面以淘宝的[robots.txt](http://www.taobao.com/robots.txt)文件为例,看看该网站对爬虫有哪些限制。
```
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
......@@ -108,17 +107,17 @@ HTTP响应(响应行+响应头+空行+消息体):
![](./res/chrome-developer-tools.png)
2. POSTMAN:功能强大的网页调试与RESTful请求工具。
2. Postman:功能强大的网页调试与RESTful请求工具。
![](./res/postman.png)
3. HTTPie:命令行HTTP客户端。
```Shell
```Bash
pip3 install httpie
```
```Shell
```Bash
http --header http://www.scu.edu.cn
HTTP/1.1 200 OK
Accept-Ranges: bytes
......@@ -138,9 +137,9 @@ HTTP响应(响应行+响应头+空行+消息体):
X-Frame-Options: SAMEORIGIN
```
4. BuiltWith:识别网站所用技术的工具。
4. `builtwith`:识别网站所用技术的工具。
```Shell
```Bash
pip3 install builtwith
```
......@@ -155,9 +154,9 @@ HTTP响应(响应行+响应头+空行+消息体):
{'web-servers': ['Tengine'], 'web-frameworks': ['Twitter Bootstrap', 'Ruby on Rails'], 'programming-languages': ['Ruby']}
```
5. python-whois:查询网站所有者的工具。
5. `python-whois`:查询网站所有者的工具。
```Shell
```Bash
pip3 install python-whois
```
......@@ -167,7 +166,7 @@ HTTP响应(响应行+响应头+空行+消息体):
{'domain_name': ['BAIDU.COM', 'baidu.com'], 'registrar': 'MarkMonitor, Inc.', 'whois_server': 'whois.markmonitor.com', 'referral_url': None, 'updated_date': [datetime.datetime(2017, 7, 28, 2, 36, 28), datetime.datetime(2017, 7, 27, 19, 36, 28)], 'creation_date': [datetime.datetime(1999, 10, 11, 11, 5, 17), datetime.datetime(1999, 10, 11, 4, 5, 17)], 'expiration_date': [datetime.datetime(2026, 10, 11, 11, 5, 17), datetime.datetime(2026, 10, 11, 0, 0)], 'name_servers': ['DNS.BAIDU.COM', 'NS2.BAIDU.COM', 'NS3.BAIDU.COM', 'NS4.BAIDU.COM', 'NS7.BAIDU.COM', 'dns.baidu.com', 'ns4.baidu.com', 'ns3.baidu.com', 'ns7.baidu.com', 'ns2.baidu.com'], 'status': ['clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited', 'clientTransferProhibited https://icann.org/epp#clientTransferProhibited', 'clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited', 'serverDeleteProhibited https://icann.org/epp#serverDeleteProhibited', 'serverTransferProhibited https://icann.org/epp#serverTransferProhibited', 'serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited', 'clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)', 'clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)', 'clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)', 'serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)', 'serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)', 'serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)'], 'emails': ['abusecomplaints@markmonitor.com', 'whoisrelay@markmonitor.com'], 'dnssec': 'unsigned', 'name': None, 'org': 'Beijing Baidu Netcom Science Technology Co., Ltd.', 'address': None, 'city': None, 'state': 'Beijing', 'zipcode': None, 'country': 'CN'}
```
6. robotparser:解析robots.txt的工具。
6. `robotparser`模块:解析`robots.txt`的工具。
```Python
>>> from urllib import robotparser
......@@ -199,110 +198,106 @@ HTTP响应(响应行+响应头+空行+消息体):
下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。
```Python
from urllib.error import URLError
from urllib.request import urlopen
import re
import pymysql
import ssl
from collections import deque
from urllib.parse import urljoin
import requests
from pymysql import Error
LI_A_PATTERN = re.compile(r'<li class="item">.*?</li>')
A_TEXT_PATTERN = re.compile(r'<a\s+[^>]*?>(.*?)</a>')
A_HREF_PATTERN = re.compile(r'<a\s+[^>]*?href="(.*?)"\s*[^>]*?>')
def decode_page(page_bytes, charsets=('utf-8',)):
"""通过指定的字符集对页面进行解码(不是每个网站都将字符集设置为utf-8)"""
page_html = None
def decode_page(page_bytes, charsets):
"""通过指定的字符集对页面进行解码"""
for charset in charsets:
try:
page_html = page_bytes.decode(charset)
break
return page_bytes.decode(charset)
except UnicodeDecodeError:
pass
# logging.error('Decode:', error)
return page_html
def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8',)):
"""获取页面的HTML代码(通过递归实现指定次数的重试操作)"""
page_html = None
try:
page_html = decode_page(urlopen(seed_url).read(), charsets)
except URLError:
# logging.error('URL:', error)
if retry_times > 0:
return get_page_html(seed_url, retry_times=retry_times - 1,
charsets=charsets)
return page_html
def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
"""从页面中提取需要的部分(通常是链接也可以通过正则表达式进行指定)"""
pattern_regex = re.compile(pattern_str, pattern_ignore_case)
return pattern_regex.findall(page_html) if page_html else []
def start_crawl(seed_url, match_pattern, *, max_depth=-1):
"""开始执行爬虫程序并对指定的数据进行持久化操作"""
conn = pymysql.connect(host='localhost', port=3306,
database='crawler', user='root',
password='123456', charset='utf8')
try:
with conn.cursor() as cursor:
url_list = [seed_url]
# 通过下面的字典避免重复抓取并控制抓取深度
visited_url_list = {seed_url: 0}
while url_list:
current_url = url_list.pop(0)
depth = visited_url_list[current_url]
if depth != max_depth:
# 尝试用utf-8/gbk/gb2312三种字符集进行页面解码
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
links_list = get_matched_parts(page_html, match_pattern)
param_list = []
for link in links_list:
if link not in visited_url_list:
visited_url_list[link] = depth + 1
page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
headings = get_matched_parts(page_html, r'<h1>(.*)<span')
if headings:
param_list.append((headings[0], link))
cursor.executemany('insert into tb_result values (default, %s, %s)',
param_list)
conn.commit()
except Error:
pass
# logging.error('SQL:', error)
finally:
conn.close()
def get_matched_parts(content_string, pattern):
"""从字符串中提取所有跟正则表达式匹配的内容"""
return pattern.findall(content_string, re.I) \
if content_string else []
def get_matched_part(content_string, pattern, group_no=1):
"""从字符串中提取跟正则表达式匹配的内容"""
match = pattern.search(content_string)
if match:
return match.group(group_no)
def get_page_html(seed_url, *, charsets=('utf-8', )):
"""获取页面的HTML代码"""
resp = requests.get(seed_url)
if resp.status_code == 200:
return decode_page(resp.content, charsets)
def repair_incorrect_href(current_url, href):
"""修正获取的href属性"""
if href.startswith('//'):
href = urljoin('http://', href)
elif href.startswith('/'):
href = urljoin(current_url, href)
return href if href.startswith('http') else ''
def start_crawl(seed_url, pattern, *, max_depth=-1):
"""开始爬取数据"""
new_urls, visited_urls = deque(), set()
new_urls.append((seed_url, 0))
while new_urls:
current_url, depth = new_urls.popleft()
if depth != max_depth:
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk'))
contents = get_matched_parts(page_html, pattern)
for content in contents:
text = get_matched_part(content, A_TEXT_PATTERN)
href = get_matched_part(content, A_HREF_PATTERN)
if href:
href = repair_incorrect_href(href)
print(text, href)
if href and href not in visited_urls:
new_urls.append((href, depth + 1))
def main():
"""主函数"""
ssl._create_default_https_context = ssl._create_unverified_context
start_crawl('http://sports.sohu.com/nba_a.shtml',
r'<a[^>]+test=a\s[^>]*href=["\'](.*?)["\']',
max_depth=2)
start_crawl(
seed_url='http://sports.sohu.com/nba_a.shtml',
pattern=LI_A_PATTERN,
max_depth=2
)
if __name__ == '__main__':
main()
```
由于使用了MySQL实现持久化操作,所以要先启动MySQL服务器并创建名为`crawler`的数据库和名为`tb_result`的二维表才能运行该程序。
### 爬虫注意事项
通过上面的例子,我们对爬虫已经有了一个感性的认识,在编写爬虫时有以下一些注意事项:
1. 处理相对链接。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接,这种情况下需要将其与URL前缀进行拼接(`urllib.parse`中的`urljoin()`函数可以完成此项操作)。
1. 上面的代码使用了`requests`三方库来获取网络资源,这是一个非常优质的三方库,关于它的用法可以参考它的[官方文档](https://requests.readthedocs.io/zh_CN/latest/)
2. 上面的代码中使用了双端队列(`deque`)来保存待爬取的URL。双端队列相当于是使用链式存储结构的`list`,在双端队列的头尾添加和删除元素性能都比较好,刚好可以用来构造一个FIFO(先进先出)的队列结构。
3. 处理相对路径。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接,这种情况下需要将其与URL前缀进行拼接(`urllib.parse`中的`urljoin()`函数可以完成此项操作)。
4. 设置代理服务。有些网站会限制访问的区域(例如美国的Netflix屏蔽了很多国家的访问),有些爬虫需要隐藏自己的身份,在这种情况下可以设置使用代理服务器,代理服务器有免费的服务器和付费的商业服务器,但后者稳定性和可用性都更好,强烈建议在商业项目中使用付费的商业代理服务器。如果使用`requests`三方库,可以在请求方法中添加`proxies`参数来指定代理服务器;如果使用标准库,可以通过修改`urllib.request`中的`ProxyHandler`来为请求设置代理服务器。
2. 设置代理服务。有些网站会限制访问的区域(例如美国的Netflix屏蔽了很多国家的访问),有些爬虫需要隐藏自己的身份,在这种情况下可以设置使用代理服务器,代理服务器有免费的服务器和付费的商业服务器,但后者稳定性和可用性都更好,强烈建议在商业项目中使用付费的代理服务器。可以通过修改`urllib.request`中的`ProxyHandler`来为请求设置代理服务器
5. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉),可以在两次获取页面数据之间添加延时从而对爬虫进行限速
3. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉),可以在两次下载之间添加延时从而对爬虫进行限速
6. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历通常会有无穷无尽的链接)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时,爬虫就不再像队列中添加该网页中的链接了
4. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历通常会有无穷无尽的链接)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时爬虫就不再像队列中添加该网页中的链接了
7. 避开蜜罐链接。网站上的有些链接是浏览器中不可见的,这种链接通常是故意诱使爬虫去访问的蜜罐,一旦访问了这些链接,服务器就会判定请求是来自于爬虫的,这样可能会导致被服务器封禁IP地址。如何避开这些蜜罐链接我们在后面为大家进行讲解
5. SSL相关问题。在使用`urlopen`打开一个HTTPS链接时会验证一次SSL证书,如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”,可以通过以下两种方式加以解决:
8. SSL相关问题。如果使用标准库的`urlopen`打开一个HTTPS链接时会验证一次SSL证书,如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”,可以通过以下两种方式加以解决:
- 使用未经验证的上下文
......
......@@ -2,12 +2,12 @@
通过上一个章节的讲解,我们已经了解到了开发一个爬虫需要做的工作以及一些常见的问题,下面我们给出一个爬虫开发相关技术的清单以及这些技术涉及到的标准库和第三方库,稍后我们会一一介绍这些内容。
1. 下载数据 - **urllib** / **requests** / **aiohttp**
1. 下载数据 - **urllib** / **requests** / **aiohttp** / **httpx**
2. 解析数据 - **re** / **lxml** / **beautifulsoup4** / **pyquery**
3. 缓存和持久化 - **pymysql** / **sqlalchemy** / **peewee**/ **redis** / **pymongo**
3. 缓存和持久化 - **mysqlclient** / **sqlalchemy** / **peewee**/ **redis** / **pymongo**
4. 生成数字签名 - **hashlib**
5. 序列化和压缩 - **pickle** / **json** / **zlib**
6. 调度器 - 多进程(**multiprocessing**) / 多线程(**threading**
6. 调度器 - **multiprocessing** / **threading** / **concurrent.futures**
### HTML页面
......@@ -79,47 +79,93 @@
</footer>
</div>
<script>
// 此处省略JavaScript代码
/* 此处省略JavaScript代码 */
</script>
</body>
</html>
```
果你对上面的代码并不感到陌生,那么你一定知道HTML页面通常由三部分构成,分别是用来承载内容的Tag(标签)、负责渲染页面的CSS(层叠样式表)以及控制交互式行为的JavaScript。通常,我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构;当然,我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
上所示的HTML页面通常由三部分构成,分别是用来承载内容的Tag(标签)、负责渲染页面的CSS(层叠样式表)以及控制交互式行为的JavaScript。通常,我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构;当然,我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
#### 使用requests获取页面
在上一节课的代码中我们使用了三方库`requests`来获取页面,下面我们对`requests`库的用法做进一步说明。
1. GET请求和POST请求。
```Python
import requests
resp = requests.get('http://www.baidu.com/index.html')
print(resp.status_code)
print(resp.headers)
print(resp.cookies)
print(resp.content.decode('utf-8'))
resp = requests.post('http://httpbin.org/post', data={'name': 'Hao', 'age': 40})
print(resp.text)
data = resp.json()
print(type(data))
```
2. URL参数和请求头。
```Python
resp = requests.get(
url='https://movie.douban.com/top250',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
)
print(resp.status_code)
```
3. 复杂的POST请求(文件上传)。
```Python
resp = requests.post(
url='http://httpbin.org/post',
files={'file': open('data.xlsx', 'rb')}
)
print(resp.text)
```
4. 操作Cookie。
```Python
cookies = {'key1': 'value1', 'key2': 'value2'}
resp = requests.get('http://httpbin.org/cookies', cookies=cookies)
print(resp.text)
jar = requests.cookies.RequestsCookieJar()
jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
resp = requests.get('http://httpbin.org/cookies', cookies=jar)
print(resp.text)
```
5. 设置代理服务器。
```Python
requests.get('https://www.taobao.com', proxies={
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
})
```
> **说明**:关于`requests`库的相关知识,还是强烈建议大家自行阅读它的[官方文档](https://requests.readthedocs.io/zh_CN/latest/)。
6. 设置请求超时。
> 说明:关于requests的详细用法可以参考它的[官方文档](http://docs.python-requests.org/zh_CN/latest/user/quickstart.html)。
```Python
requests.get('https://github.com', timeout=0.01)
```
### 页面解析
......@@ -131,13 +177,41 @@
| XPath解析 | lxml | 快 | 一般 | 需要安装C语言依赖库<br/>唯一支持XML的解析器 |
| CSS选择器解析 | bs4 / pyquery | 不确定 | 简单 | |
> 说明:BeautifulSoup可选的解析器包括:Python标准库(html.parser)、lxml的HTML解析器、lxml的XML解析器和html5lib
> **说明**:`BeautifulSoup`可选的解析器包括:Python标准库中的`html.parser`、`lxml`的HTML解析器、`lxml`的XML解析器和`html5lib`
#### 使用正则表达式解析页面
如果你对正则表达式没有任何的概念,那么推荐先阅读[《正则表达式30分钟入门教程》](),然后再阅读我们之前讲解在Python中如何使用正则表达式一文。
下面的例子演示了如何用正则表达式解析“豆瓣电影Top250”中的中文电影名称。
```Python
import random
import re
import time
import requests
PATTERN = re.compile(r'<a[^>]*?>\s*<span class="title">(.*?)</span>')
for page in range(10):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={page * 25}',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
},
)
items = PATTERN.findall(resp.text)
for item in items:
print(item)
time.sleep(random.randint(1, 5))
```
#### XPath解析和lxml
......@@ -196,13 +270,37 @@ XPath还支持通配符用法,如下所示。
| //title \| //price | 选取文档中的所有 title 和 price 元素。 |
| /bookstore/book/title \| //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
> 说明:上面的例子来自于菜鸟教程网站上[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
> **说明**:上面的例子来自于菜鸟教程网站上[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
当然,如果不理解或者不太熟悉XPath语法,可以在Chrome浏览器中按照如下所示的方法查看元素的XPath语法。
![](./res/douban-xpath.png)
下面的例子演示了如何用XPath解析“豆瓣电影Top250”中的中文电影名称。
```Python
from lxml import etree
import requests
for page in range(10):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={page * 25}',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
)
html = etree.HTML(resp.text)
spans = html.xpath('/html/body/div[3]/div[1]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]')
for span in spans:
print(span.text)
```
### BeautifulSoup的使用
......@@ -219,42 +317,63 @@ BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库。它
- find / find_all
- select_one / select
> 说明:更多内容可以参考BeautifulSoup的[官方文档](https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html)。
> **说明**:更多内容可以参考BeautifulSoup的[官方文档](https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/)。
下面的例子演示了如何用CSS选择器解析“豆瓣电影Top250”中的中文电影名称。
```Python
import random
import time
import bs4
import requests
### PyQuery的使用
for page in range(10):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={page * 25}',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
},
)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
elements = soup.select('.info>div>a')
for element in elements:
span = element.select_one('.title')
print(span.text)
time.sleep(random.random() * 5)
pyquery相当于jQuery的Python实现,可以用于解析HTML网页。
```
### 实例 - 获取知乎发现上的问题链接
### 例子 - 获取知乎发现上的问题链接
```Python
import re
from urllib.parse import urljoin
import re
import bs4
import requests
from bs4 import BeautifulSoup
def main():
headers = {'user-agent': 'Baiduspider'}
proxies = {
'http': 'http://122.114.31.177:808'
}
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
resp = requests.get(seed_url,
headers=headers,
proxies=proxies)
soup = BeautifulSoup(resp.text, 'lxml')
resp = requests.get(urljoin(base_url, 'explore'), headers=headers)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
link_set = set()
links_set = set()
for a_tag in soup.find_all('a', {'href': href_regex}):
if 'href' in a_tag.attrs:
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
link_set.add(full_url)
print('Total %d question pages found.' % len(link_set))
links_set.add(full_url)
print('Total %d question pages found.' % len(links_set))
print(links_set)
if __name__ == '__main__':
......
......@@ -2,60 +2,112 @@
### 存储海量数据
数据持久化的首选方案应该是关系型数据库,关系型数据库的产品很多,包括:Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据,文档数据库也是不错的选择,MongoDB是文档数据库中的佼佼者,之前我们已经讲解过MongDB的相关知识,在此不再进行赘述
数据持久化的首选方案应该是关系型数据库,关系型数据库的产品很多,包括:Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据,文档数据库也是不错的选择,MongoDB是文档数据库中的佼佼者,有兴趣的读者可以自行研究
### 数据缓存
下面的代码演示了如何使用MySQL来保存从知乎发现上爬取到的链接和页面。
通过[《网络数据采集和解析》](./02.数据采集和解析.md)一文,我们已经知道了如何从指定的页面中抓取数据,以及如何保存抓取的结果,但是我们没有考虑过这么一种情况,就是我们可能需要从已经抓取过的页面中提取出更多的数据,重新去下载这些页面对于规模不大的网站倒是问题也不大,但是如果能够把这些页面缓存起来,对应用的性能会有明显的改善。可以使用Redis来提供高速缓存服务,关于Redis的知识,我们在[《NoSQL入门》](../Day36-40/NoSQL入门.md)一文中已经做过简要的介绍。
```SQL
create database zhihu default charset utf8;
create user 'hellokitty'@'%' identified by 'Hellokitty.618';
grant all privileges on zhihu.* to 'hellokitty'@'%';
flush privileges;
### 实例 - 缓存知乎发现上的链接和页面代码
use zhihu;
create table `tb_explore`
(
`id` integer auto_increment,
`url` varchar(1024) not null,
`page` longblob not null,
`digest` char(48) unique not null,
`idate` datetime default now(),
primary key (`id`)
);
```
```Python
from hashlib import sha1
import hashlib
import pickle
import re
import zlib
from urllib.parse import urljoin
import MySQLdb
import bs4
import requests
conn = MySQLdb.connect(host='1.2.3.4', port=3306,
user='hellokitty', password='Hellokitty.618',
database='zhihu', charset='utf8',
autocommit=True)
def write_to_db(url, page, digest):
try:
with conn.cursor() as cursor:
cursor.execute(
'insert into tb_explore (url, page, digest) values (%s, %s, %s) ',
(url, page, digest)
)
except MySQLdb.MySQLError as err:
print(err)
def main():
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
headers = {'user-agent': 'Baiduspider'}
try:
resp = requests.get(seed_url, headers=headers)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
for a_tag in soup.find_all('a', {'href': href_regex}):
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
digest = hashlib.sha1(full_url.encode()).hexdigest()
html_page = requests.get(full_url, headers=headers).text
zipped_page = zlib.compress(pickle.dumps(html_page))
write_to_db(full_url, zipped_page, digest)
finally:
conn.close()
if __name__ == '__main__':
main()
```
### 数据缓存
通过[《网络数据采集和解析》](./67.数据采集和解析.md)一文,我们已经知道了如何从指定的页面中抓取数据,以及如何保存抓取的结果,但是我们没有考虑过这么一种情况,就是我们可能需要从已经抓取过的页面中提取出更多的数据,重新去下载这些页面对于规模不大的网站倒是问题也不大,但是如果能够把这些页面缓存起来,对应用的性能会有明显的改善。下面的例子演示了如何使用Redis来缓存知乎发现上的页面。
```Python
import hashlib
import pickle
import re
import requests
import zlib
from urllib.parse import urljoin
from bs4 import BeautifulSoup
from redis import Redis
import bs4
import redis
import requests
def main():
# 指定种子页面
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
# 创建Redis客户端
client = Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
# 设置用户代理(否则访问会被拒绝)
client = redis.Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
headers = {'user-agent': 'Baiduspider'}
# 通过requests模块发送GET请求并指定用户代理
resp = requests.get(seed_url, headers=headers)
# 创建BeautifulSoup对象并指定使用lxml作为解析器
soup = BeautifulSoup(resp.text, 'lxml')
soup = bs4.BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
# 将URL处理成SHA1摘要(长度固定更简短)
hasher_proto = sha1()
# 查找所有href属性以/question打头的a标签
for a_tag in soup.find_all('a', {'href': href_regex}):
# 获取a标签的href属性值并组装完整的URL
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
# 传入URL生成SHA1摘要
hasher = hasher_proto.copy()
hasher.update(full_url.encode('utf-8'))
field_key = hasher.hexdigest()
# 如果Redis的键'zhihu'对应的hash数据类型中没有URL的摘要就访问页面并缓存
if not client.hexists('zhihu', field_key):
field_key = hashlib.sha1(full_url.encode()).hexdigest()
if not client.hexists('spider:zhihu:explore', field_key):
html_page = requests.get(full_url, headers=headers).text
# 对页面进行序列化和压缩操作
zipped_page = zlib.compress(pickle.dumps(html_page))
# 使用hash数据类型保存URL摘要及其对应的页面代码
client.hset('zhihu', field_key, zipped_page)
# 显示总共缓存了多少个页面
print('Total %d question pages found.' % client.hlen('zhihu'))
client.hset('spider:zhihu:explore', field_key, zipped_page)
print('Total %d question pages found.' % client.hlen('spider:zhihu:explore'))
if __name__ == '__main__':
......
## 并发下载
### 多线程和多进程回顾
在前面的[《进程和线程》](../Day01-15/Day13/进程和线程.md)一文中,我们已经对在Python中使用多进程和多线程实现并发编程进行了简明的讲解,在此我们补充几个知识点。
### 多线程和多进程补充知识点
#### threading.local类
使用线程时最不愿意遇到的情况就是多个线程竞争资源,在这种情况下为了保证资源状态的正确性,我们可能需要对资源进行加锁保护的处理,这一方面会导致程序失去并发性,另外如果多个线程竞争多个资源时,还有可能因为加锁方式的不当导致[死锁](https://zh.wikipedia.org/wiki/%E6%AD%BB%E9%94%81)。要解决多个线程竞争资源的问题,其中一个方案就是让每个线程都持有资源的副本(拷贝),这样每个线程可以操作自己所持有的资源,从而规避对资源的竞争。
要实现将资源和持有资源的线程进行绑定的操作,最简单的做法就是使用threading模块的local类,在网络爬虫开发中,就可以使用local类为每个线程绑定一个MySQL数据库连接或Redis客户端对象,这样通过线程可以直接获得这些资源,既解决了资源竞争的问题,又避免了在函数和方法调用时传递这些资源。具体的请参考本章多线程爬取“手机搜狐网”(Redis版)的实例代码。
要实现将资源和持有资源的线程进行绑定的操作,最简单的做法就是使用`threading`模块的`local`类,在网络爬虫开发中,就可以使用`local`类为每个线程绑定一个MySQL数据库连接或Redis客户端对象,这样通过线程可以直接获得这些资源,既解决了资源竞争的问题,又避免了在函数和方法调用时传递这些资源。具体的请参考本章多线程爬取“手机搜狐网”(Redis版)的实例代码。
#### concurrent.futures模块
......@@ -29,7 +27,7 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
1. 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。
2. 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。
> 说明:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它的长处,如果要提升CPU的利用率可以考虑“多进程+协程”的模式。
> **说明**:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它擅长的,如果要提升CPU的利用率可以考虑“多进程+多线程”或者“多进程+协程”的工作模式。
#### 历史回顾
......@@ -39,176 +37,39 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
4. Python 3.4:引入`asyncio.coroutine`装饰器用来标记作为协程的函数,协程函数和`asyncio`及其事件循环一起使用,来实现异步I/O操作。
5. Python 3.5:引入了`async``await`,可以使用`async def`来定义一个协程函数,这个函数中不能包含任何形式的`yield`语句,但是可以使用`return``await`从协程中返回值。
#### 示例代码
1. 生成器 - 数据的生产者。
```Python
from time import sleep
# 倒计数生成器
def countdown(n):
while n > 0:
yield n
n -= 1
def main():
for num in countdown(5):
print(f'Countdown: {num}')
sleep(1)
print('Countdown Over!')
if __name__ == '__main__':
main()
```
生成器还可以叠加来组成生成器管道,代码如下所示。
```Python
# Fibonacci数生成器
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
# 偶数生成器
def even(gen):
for val in gen:
if val % 2 == 0:
yield val
def main():
gen = even(fib())
for _ in range(10):
print(next(gen))
if __name__ == '__main__':
main()
```
2. 协程 - 数据的消费者。
```Python
from time import sleep
# 生成器 - 数据生产者
def countdown_gen(n, consumer):
consumer.send(None)
while n > 0:
consumer.send(n)
n -= 1
consumer.send(None)
# 协程 - 数据消费者
def countdown_con():
while True:
n = yield
if n:
print(f'Countdown {n}')
sleep(1)
else:
print('Countdown Over!')
def main():
countdown_gen(5, countdown_con())
if __name__ == '__main__':
main()
```
> 说明:上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器,通俗的说就是让生成器执行到有yield关键字的地方挂起,当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。
```Python
from functools import wraps
def coroutine(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
gen = fn(*args, **kwargs)
next(gen)
return gen
return wrapper
```
这样就可以使用`@coroutine`装饰器对协程进行预激操作,不需要再写重复代码来激活协程。
3. 异步I/O - 非阻塞式I/O操作。
```Python
import asyncio
@asyncio.coroutine
def countdown(name, n):
while n > 0:
print(f'Countdown[{name}]: {n}')
yield from asyncio.sleep(1)
n -= 1
def main():
loop = asyncio.get_event_loop()
tasks = [
countdown("A", 10), countdown("B", 5),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
```
4. `async``await`
```Python
import asyncio
import aiohttp
async def download(url):
print('Fetch:', url)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(url, '--->', resp.status)
print(url, '--->', resp.cookies)
print('\n\n', await resp.text())
def main():
loop = asyncio.get_event_loop()
urls = [
'https://www.baidu.com',
'http://www.sohu.com/',
'http://www.sina.com.cn/',
'https://www.taobao.com/',
'https://www.jd.com/'
]
tasks = [download(url) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
```
上面的代码使用了[AIOHTTP](https://github.com/aio-libs/aiohttp)这个非常著名的第三方库,它实现了HTTP客户端和HTTP服务器的功能,对异步操作提供了非常好的支持,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)
协程实现了协作式并发,通过提高CPU的利用率来达到改善性能的目的。著名的三方库[`aiohttp`](https://github.com/aio-libs/aiohttp)就是通过协程的方式实现了HTTP客户端和HTTP服务器的功能,较之`requests`有更好的获取数据的性能,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)
```Python
import asyncio
import aiohttp
async def download(url):
print('Fetch:', url)
async with aiohttp.ClientSession() as session:
async with session.get(url, ssl=False) as resp:
print(url, '--->', resp.status)
print(url, '--->', resp.headers)
print('\n\n', await resp.text())
def main():
loop = asyncio.get_event_loop()
urls = [
'https://www.baidu.com',
'http://www.sohu.com/',
'http://www.sina.com.cn/',
'https://www.taobao.com/',
'http://jd.com/'
]
tasks = [download(url) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
```
### 实例 - 多线程爬取“手机搜狐网”所有页面
......
......@@ -79,4 +79,4 @@ selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executabl
export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
```
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。当然,更为简单的办法是把chromedriver直接放在虚拟环境中,跟Python解释器位于同一个路径下就可以了。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册