推荐系统:实现文章相似推荐的简单实例

看了一篇文章实现了文章的内容相似度计算实现相似推荐,算法比较简单,非常适合我这种初学入门的人。

来自一篇英文文章:地址

文章标题为:How to build a content-based movie recommender system with Natural Language Processing

文章的代码在:地址

该文章实现相似推荐的步骤:

1、将CSV加载到pandas.pd

2、提取其中的标题、题材分类、导演、演员、情节描述4个字段;

3、将单词都变小写,人名中的空格合并(英文才需要这样);

4、题材分类、导演、演员这几个特征都是结构化的不需要处理;而标题、情节描述这类字段是长段文本,使用nltk库做关键词提取(如果是中文可以用jieba分词库也有关键词提取功能)

5、将第四步骤的分类、导演、演员、关键词列表,合并到一个词列表(这一处理其实暗含了分类、导演、演员三个特征和关键词一样重要,没有做加权处理)

6、使用CountVectorizer做每个词语的计数,得到了每个文章的向量;

7、使用sklearn的cosin做笛卡尔积的相似度计算;

8、计算结果是一个二维矩阵,按行查询某一个文章的推荐结果,按相似度值排序得到最相似的文章

从里面能学到不少知识的运用:

1、全流程用pandas运行,尤其是for each row,做单个列的各种map计算;

2、计算相似度时使用了多个特征,包括Title,Genre,Director,Actors,Plot,统一成一个bag of words参与计算

3、使用from sklearn.metrics.pairwise import cosine_similarity用于相似度计算;

4、使用from sklearn.feature_extraction.text import CountVectorizer用于单词计数;

5、使用from rake_nltk import Rake用于关键词提取;

代码实现关键部分:

作者用到的一些库:

import pandas as pd
from rake_nltk import Rake
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import CountVectorizer

pandas的dataframe中,直接替换一列的语法:

df['Director'] = df['Director'].map(lambda x: x.split(' '))

pd按行以此处理某个列的方法:

for index, row in df.iterrows():
    row['Actors'] = [x.lower().replace(' ','') for x in row['Actors']]
    row['Director'] = ''.join(row['Director']).lower()

pd.df删除某一列的方法:

df.drop(columns = ['Plot'], inplace = True)

pd.df从columns中提取一列作为index的方法:

df.set_index('Title', inplace = True)

作者将能使用的所有列,都放在了一个词包中用于相似度计算,按我的想法,这些特征列其实应该有不同的权重?

df['bag_of_words'] = ''
columns = df.columns
for index, row in df.iterrows():
    words = ''
    for col in columns:
        if col != 'Director':
            words = words + ' '.join(row[col])+ ' '
        else:
            words = words + row[col]+ ' '
    row['bag_of_words'] = words
    
df.drop(columns = [col for col in df.columns if col!= 'bag_of_words'], inplace = True)

sklearn使用词计数的调用:

count = CountVectorizer()
count_matrix = count.fit_transform(df['bag_of_words'])

sklearn实现矩阵相似度计算的方法:

# generating the cosine similarity matrix
cosine_sim = cosine_similarity(count_matrix, count_matrix)

怎样实现不同特征列的融合相似度计算?

这个问题纠结我很久,查询了一些文章,大都是人工指定加权权重,或者使用模型拟合权重值,没有多么简单的方法,而作者使用的其实是直接把分类、演员等字段,和关键词直接融合的方法

作者在文章中提到一句话:

I decided to use CountVectorizer rather than TfIdfVectorizer for one simple reason: I need a simple frequency counter for each word in my bag_of_words column. Tf-Idf tends to give less importance to the words that are more present in the entire corpus (our whole column, in this case) which is not what we want for this application, because every word is important to detect similarity!

对于标题、介绍这种纯文本内容,我们可以用TF/IDF提取关键词,物理含义就是降低全局出现的词频很多的词语;但是其实对于作者、演员、题材这类特征列,他们并不需要降低全局词频,使用词频计数即可。

有哪些可以提升的地方

作者的方法确实可以实现相似推荐,不过我感觉有一些可以提升的地方:

1、标题、简介,提取关键词后,可以查询业界的word2vec做向量扩展,这样能实现恰恰和伦巴舞这类词语的相似度度量,直接的关键词查询是得不到这样的信息;

2、分类、导演、演员这三个特征,需要和描述得到的关键词区分开,可以用加权的方法进行,按照产品的需求,加重分类、导演的相似度权重,降低演员、关键词的权重等,如果需要可以从点击率等出发,用模型计算这些权重;

本文地址:http://www.crazyant.net/2454.html,转载请注明来源

 

Pandas中对轴axis=0和axis=1的理解

刚学习numpy和Pandas,被axis、axis=0或者axis=’index’,axis=1或者axis=’columns’给搞蒙了,甚至经常觉得书是不是写错了,有点反直觉。

来自简书的一篇文章地址有张图解释的挺好的,见文章底部

 

引用一下这篇文章的话,理解的很好:

实际上axis = 1,指的是沿着行求所有列的平均值,代表了横轴,那axis = 0,就是沿着列求所有行的平均值,代表了纵轴。

但理解起来还是很绕,按我个人的理解,如果对比excel和MySQL的数据表来理解就更容易:

我们正常在使用excel或者mysql的时候,默认都是一行代表一条数据,每个列是不同的信息字段,我们在读取表的时候,都是一行一行读取的,如果要算max、min、sum等函数,其实都是一行一行计算全局的min、max、sum,但是算出来其实是每个列的数据的min、max、sum,这其实就是axis=0和axis=’index’的意思:一行一行的计算,但是算出来其实是每列的结果;

相对应的,我们的excel、mysql,很少会实现跨列做计算,除非每列都是一样的数字信息,举个例子某一个数据表的每一行有一个主键是日期,每一列是对应每个页面的PV,那么我可以计算每个日期的PV总数、平均数,这时候就是跨列计算了,就是不常见的axis=1或者axis=’columns’,虽然是跨列计算,但是算出来的结果其实是行标签日期的数据结果。

axis=0,虽然是一行一行计算,其实算出来是每列的结果,换句话说,aixs=0是指计算的时候跨行、每行每行的算,那么算出来当然是每列的结果,就像一把梳子往下梳,得到的就是树条状的结果,每个竖条的标签当然就是columns的标签

axis=1,虽然是一列一列计算,其实算出来是每行的结果,换句话说,aixs=1是指计算的时候跨列、每列每列的算,那么算出来当然是每行的结果,就像一把梳子往右梳,得到的就是横条状的结果,每个横条的标签当然是index的标签

以上当然是自己的理解,具体还得自己琢磨才可以弄清楚。

Java和Python使用Grpc访问Tensorflow的Serving代码

发现网上大量的代码都是mnist,我自己反正不是搞图像处理的,所以这个例子我怎么都不想搞;

wide&deep这种,包含各种特征的模型,才是我的需要,iris也是从文本训练模型,所以非常简单;

本文给出Python和Java访问Tensorflow的Serving代码。

Java版本使用Grpc访问Tensorflow的Serving代码

package io.github.qf6101.tensorflowserving;
 
import com.google.protobuf.ByteString;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import org.tensorflow.example.*;
import org.tensorflow.framework.DataType;
import org.tensorflow.framework.TensorProto;
import org.tensorflow.framework.TensorShapeProto;
import tensorflow.serving.Model;
import tensorflow.serving.Predict;
import tensorflow.serving.PredictionServiceGrpc;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * 参考:https://www.jianshu.com/p/d82107165119
 * 参考:https://github.com/grpc/grpc-java
 */
public class PssIrisGrpcClient {
 
    public static Example createExample() {
        Features.Builder featuresBuilder = Features.newBuilder();
 
        Map<String, Float> dataMap = new HashMap<String, Float>();
        dataMap.put("SepalLength", 5.1f);
        dataMap.put("SepalWidth", 3.3f);
        dataMap.put("PetalLength", 1.7f);
        dataMap.put("PetalWidth", 0.5f);
 
        Map<String, Feature> featuresMap = mapToFeatureMap(dataMap);
        featuresBuilder.putAllFeature(featuresMap);
 
        Features features = featuresBuilder.build();
        Example.Builder exampleBuilder = Example.newBuilder();
        exampleBuilder.setFeatures(features);
        return exampleBuilder.build();
    }
 
    private static Map<String, Feature> mapToFeatureMap(Map<String, Float> dataMap) {
        Map<String, Feature> resultMap = new HashMap<String, Feature>();
        for (String key : dataMap.keySet()) {
            // // data1 = {"SepalLength":5.1,"SepalWidth":3.3,"PetalLength":1.7,"PetalWidth":0.5}
            FloatList floatList = FloatList.newBuilder().addValue(dataMap.get(key)).build();
            Feature feature = Feature.newBuilder().setFloatList(floatList).build();
            resultMap.put(key, feature);
        }
        return resultMap;
    }
 
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 8888;
 
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
                // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
                // needing certificates.
                .usePlaintext()
                .build();
        PredictionServiceGrpc.PredictionServiceBlockingStub blockingStub = PredictionServiceGrpc.newBlockingStub(channel);
 
        com.google.protobuf.Int64Value version = com.google.protobuf.Int64Value.newBuilder()
                .setValue(1)
                .build();
 
        Model.ModelSpec modelSpec = Model.ModelSpec.newBuilder()
                .setName("iris")
                .setVersion(version)
                .setSignatureName("classification")
                .build();
 
        List<ByteString> exampleList = new ArrayList<ByteString>();
        exampleList.add(createExample().toByteString());
 
        TensorShapeProto.Dim featureDim = TensorShapeProto.Dim.newBuilder().setSize(exampleList.size()).build();
        TensorShapeProto shapeProto = TensorShapeProto.newBuilder().addDim(featureDim).build();
        org.tensorflow.framework.TensorProto tensorProto = TensorProto.newBuilder().addAllStringVal(exampleList).setDtype(DataType.DT_STRING).setTensorShape(shapeProto).build();
 
        Predict.PredictRequest request = Predict.PredictRequest.newBuilder()
                .setModelSpec(modelSpec)
                .putInputs("inputs", tensorProto)
                .build();
        tensorflow.serving.Predict.PredictResponse response = blockingStub.predict(request);
        System.out.println(response);
 
        channel.shutdown();
    }
}

需要增加如下maven依赖:

        <!-- https://mvnrepository.com/artifact/org.tensorflow/tensorflow -->
        <dependency>
            <groupId>org.tensorflow</groupId>
            <artifactId>tensorflow</artifactId>
            <version>1.12.0</version>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-netty -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>1.20.0</version>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-protobuf -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.20.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-stub -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.20.0</version>
        </dependency>

输出结果:

outputs {
  key: "scores"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 3
      }
    }
    float_val: 0.9997806
    float_val: 2.1938368E-4
    float_val: 1.382611E-9
  }
}
outputs {
  key: "classes"
  value {
    dtype: DT_STRING
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 3
      }
    }
    string_val: "0"
    string_val: "1"
    string_val: "2"
  }
}

Python版本使用Grpc访问Tensorflow的Serving代码

# 创建 gRPC 连接
import pandas as pd
from grpc.beta import implementations
import tensorflow as tf
from tensorflow_serving.apis import prediction_service_pb2, classification_pb2
 
#channel = implementations.insecure_channel('127.0.0.1', 8500):8888
channel = implementations.insecure_channel('127.0.0.1', 8888)
stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
 
def _create_feature(v):
    return tf.train.Feature(float_list=tf.train.FloatList(value=[v]))
 
data1 = {"SepalLength":5.1,"SepalWidth":3.3,"PetalLength":1.7,"PetalWidth":0.5}
features1 = {k: _create_feature(v) for k, v in data1.items()}
example1 = tf.train.Example(features=tf.train.Features(feature=features1))
 
 
data2 = {"SepalLength":1.1,"SepalWidth":1.3,"PetalLength":1.7,"PetalWidth":0.5}
features2 = {k: _create_feature(v) for k, v in data2.items()}
example2 = tf.train.Example(features=tf.train.Features(feature=features2))
 
# 获取测试数据集,并转换成 Example 实例。
examples = [example1, example2]
 
# 准备 RPC 请求,指定模型名称。
request = classification_pb2.ClassificationRequest()
request.model_spec.name = 'iris'
request.input.example_list.examples.extend(examples)
 
# 获取结果
response = stub.Classify(request, 10.0)
print(response)

Python代码看起来简单不少,但是我们的线上服务都是Java,所以不好集成的,只能做一些离线的批量预测;

输出如下:

result {
  classifications {
    classes {
      label: "0"
      score: 0.9997805953025818
    }
    classes {
      label: "1"
      score: 0.00021938368445262313
    }
    classes {
      label: "2"
      score: 1.382611025668723e-09
    }
  }
  classifications {
    classes {
      label: "0"
      score: 0.0736534595489502
    }
    classes {
      label: "1"
      score: 0.8393719792366028
    }
    classes {
      label: "2"
      score: 0.08697459846735
    }
  }
}
model_spec {
  name: "iris"
  version {
    value: 1
  }
  signature_name: "serving_default"
}

个人其实非常喜欢HTTP+JSON接口,完全不用搞这么多grpc这些麻烦的东西,尤其Java的grpc,遇到好多问题好崩溃;

不过号称grpc比http性能好不少,线上只能用grpc。

Flask怎样从其他Python文件导入app.route视图函数

用Blueprint这个东西实现;

主文件:

flask_main.py

import flask

from flask_pyecharts.flask_moudle2 import account_api

app = flask.Flask(__name__)

app.register_blueprint(account_api)

@app.route("/hello")
def hello():
    return "hello"

app.run()

引入的一个Module的文件,这个文件中写了视图函数

flask_moudle2.py

from flask import Blueprint

account_api = Blueprint('account_api', __name__)

@account_api.route("/account")
def accountList():
    return "list of accounts"

界面上访问第一个函数和第二个函数都返回正常

贴一下官网蓝图的解释:

Flask 用 蓝图(blueprints) 的概念来在一个应用中或跨应用制作应用组件和支持通用的模式。蓝图很好地简化了大型应用工作的方式,并提供给 Flask 扩展在应用上注册操作的核心方法。一个 Blueprint 对象与 Flask 应用对象的工作方式很像,但它确实不是一个应用,而是一个描述如何构建或扩展应用的 蓝图 。

每个蓝图可以指定自己的templates和static文件夹,指定方法就是Blueprint的参数

地址:http://docs.jinkan.org/docs/flask/blueprints.html

参考:https://stackoverflow.com/questions/15231359/split-python-flask-app-into-multiple-files/15231623

Python3用scan和delete命令批量清理redis数据

import redis
import time

redis_cache = redis.Redis(host="", port=0000, password="xxxx", db=1)

print("begin")

print(redis_cache.dbsize())

begin_pos = 0
while True:
    result = redis_cache.scan(begin_pos, "xxxxx_*", 100000)
    return_pos, datalist = result
    print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), return_pos)
    if len(datalist) > 0:
        print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), "delete", len(datalist))
        redis_cache.delete(*datalist)

    if return_pos == 0:
        break
    begin_pos = return_pos

print("over")

有几个知识点:

  1. scan命令的第一个入参从0开始代表从头扫描
  2. scan命令的返回是(pos, [])元组,第一个pos可以作为下次扫描的入参pos,第二个值就是扫描到的KEY
  3. scan命令的第三个参数,是本次扫描的KEY数目,比如传递10万个,但是返回的LIST可能是0,因为有0个匹配
  4. redis的delete命令传递的是array参数,可以用*list传递

 

CentOS自己编译安装Python3的命令

yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make libffi-devel

 

参照:https://blog.51cto.com/13544424/2149473

解决缺少SSL问题(安装1.1.1版本),然后往下走

 

./configure prefix=/usr/local/python3 –with-openssl=/usr/local/openssl

make && make install

/usr/local/python3/bin/python3 -m pip install –upgrade pip

/usr/local/python3/bin/python3 -m pip install redis

PyCharm开发PySpark程序的配置和实例

对于PyCharm,需要作如下设置:
1、安装pyspark,它会自动安装py4j
2、在edit configuration中,add content root,选择spark下载包的python/pyspark/lib下的pyspark.zip和py4j.zip两个包;

代码实例:

from pyspark.sql import Row
from pyspark.sql import SparkSession

logFile = "file:///Users/peishuaishuai/tmp/sparktest.txt"  # Should be some file on your system
spark = SparkSession.builder.appName("SimpleApp").getOrCreate()

input = spark.read.text(logFile).rdd.map(
    lambda x: str(x[0]).split("\t")
).filter(
    lambda x: len(x) == 2
).map(
    lambda x: Row(name=x[0], grade=int(x[1]))
)

schemaData = spark.createDataFrame(input)
schemaData.createOrReplaceTempView("tb")

print(schemaData.count())
schemaData.printSchema()

datas = spark.sql("select name,sum(grade) from tb group by name").rdd.map(
    lambda x: "\t".join([x[0], str(x[1])])
)

datas.repartition(3).saveAsTextFile("file:///Users/peishuaishuai/tmp/sparktest_output")

spark.stop()

 

输入数据为:

name1	11
name2	12
name3	13
name4	14
name5	15
name1	16
name2	17
name3	18
name4	19
name5	20
name11	21
name12	22
name1	23
name2	24
name3	25
name4	26
name5	27
name18	28
name19	29
name20	30
name21	31
name1	32
name2	33
name3	34
name4	35
name5	36
name27	37
name28	38
name29	39
name1	40
name2	41
name3	42
name4	43

输出 print结果为:

33
root
 |-- grade: long (nullable = true)
 |-- name: string (nullable = true)

文件中内容为:

name3	132
name19	29
name2	127
name12	22
name11	21
name20	30
name28	38
name27	37
name5	98
name29	39
name21	31
name4	137
name1	122
name18	28

pyspark开发起来,有点问题就是当级联过多的时候,类型可能丢失,导致代码没有提示,这点很不爽。

其实对比了python、scala、java,我觉得编写大型的spark代码,用Java是最靠谱的,因为它强类型,代码提示很爽很直观。

 

Python高级编程技巧

ipython的使用

地址:https://ipython.org/install.html

简单的安装方法:pip install ipython

一些方便的使用方法:

  • 输入要查看的对象,然后输入一个问号可以查看API,输入两个问号可以查看代码
  • 可以直接调用shell命令,在前面带上!即可
  • 按Tab可以自动语法补全
  • 最近的命令输出结果,可以从_、__、___三个变量获得
  • %hist或者%history查看历史命令
  • %timeit可以进行命令的执行时间测试

数据结构与算法

列表生成器

In [5]: a=[1,2,3]

In [6]: [x*x for x in a if x>1]
Out[6]: [4, 9]

集合生成器(和列表生成器相同)

In [7]: a=[1,2,3]

In [8]: s = {x*x for x in a if x>1}

In [9]: s
Out[9]: {4, 9}

In [10]: type(s)
Out[10]: set

字典生成器

In [11]: a=[1,2,3]

In [12]: {str(x):x+1 for x in a if x>1}
Out[12]: {'2': 3, '3': 4}

range和xrange的使用

In [13]: range?
Docstring:
range(stop) -> list of integers
range(start, stop[, step]) -> list of integers

In [14]: range(10)
Out[14]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [15]: range(3,10)
Out[15]: [3, 4, 5, 6, 7, 8, 9]

In [16]: xrange?
Docstring:
xrange(stop) -> xrange object
xrange(start, stop[, step]) -> xrange object

In [19]: list(xrange(3,10))
Out[19]: [3, 4, 5, 6, 7, 8, 9]

In [21]: list(xrange(10))
Out[21]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

range和xrange的用法相同,只是range会直接生成一个内存列表,xrange会生成一个生产器,xrange的效率更高,更节省内存

filter用于过滤数据

In [22]: filter?
Docstring:
filter(function or None, sequence) -> list, tuple, or string

Return those items of sequence for which function(item) is true.  If
function is None, return the items that are true.  If sequence is a tuple
or string, return the same type, else return a list.
Type:      builtin_function_or_method

In [23]: filter(lambda x:x%3==0, xrange(10))
Out[23]: [0, 3, 6, 9]

使用collections.namedtuple给列表或者元组命名

In [24]: from collections import namedtuple

In [30]: Point = namedtuple('Point', ['x', 'y'])

In [31]: p = Point(11, 22)

In [32]: p.__dict__
Out[32]: OrderedDict([('x', 11), ('y', 22)])

In [33]: p.x
Out[33]: 11

In [34]: p.y
Out[34]: 22

random的使用

In [35]: from random import randint

In [36]: randint?
Signature: randint(a, b)
Docstring:
Return random integer in range [a, b], including both end points.

File:      /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/random.py
Type:      instancemethod

In [37]: randint(1,10)
Out[37]: 2

统计序列元素的频度和TOP N

In [42]: from collections import Counter

In [43]: Counter?

In [44]: c = Counter('aaabbbbccccccddddddeeeeee')

In [45]: c
Out[45]: Counter({'a': 3, 'b': 4, 'c': 6, 'd': 6, 'e': 6})

In [46]: c.most_common(3)
Out[46]: [('c', 6), ('e', 6), ('d', 6)]

将字典按value排序

In [47]: from random import randint

In [48]: keys = 'abcdefg'

In [50]: d = {x:randint(90,100) for x in keys}

In [51]: d
Out[51]: {'a': 90, 'b': 100, 'c': 94, 'd': 97, 'e': 94, 'f': 95, 'g': 91}

In [53]: d.items()
Out[53]: [('a', 90), ('c', 94), ('b', 100), ('e', 94), ('d', 97), ('g', 91), ('f', 95)]

In [54]: sorted?
Docstring: sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list
Type:      builtin_function_or_method

In [55]: sorted(d.items(), key=lambda x : x[1])
Out[55]: [('a', 90), ('g', 91), ('c', 94), ('e', 94), ('f', 95), ('d', 97), ('b', 100)]

获得多个词典的key的交集

In [99]: from random import randint, sample

In [100]: dd1 = {x:randint(90,100) for x in sample('abcdefghij', 5)}

In [101]: dd2 = {x:randint(90,100) for x in sample('abcdefghij', 5)}

In [102]: dd3 = {x:randint(90,100) for x in sample('abcdefghij', 5)}

In [103]: dd1
Out[103]: {'b': 100, 'c': 97, 'd': 100, 'e': 97, 'f': 92}

In [104]: dd2
Out[104]: {'a': 100, 'c': 90, 'g': 93, 'h': 93, 'j': 90}

In [105]: dd3
Out[105]: {'c': 96, 'e': 93, 'f': 91, 'h': 97, 'j': 90}

In [106]: mp = map(dict.viewkeys, (dd1, dd2, dd3))

In [107]: mp
Out[107]:
[dict_keys(['c', 'b', 'e', 'd', 'f']),
 dict_keys(['a', 'h', 'c', 'j', 'g']),
 dict_keys(['h', 'c', 'j', 'e', 'f'])]

In [108]: reduce(lambda x,y: x&y, mp)
Out[108]: {'c'}

怎样让字典按照插入有序

In [122]: from collections import OrderedDict

In [123]: d = OrderedDict()

In [124]: d['x'] = 1

In [125]: d['y'] = 2

In [126]: d['a'] = 2

In [127]: d['b'] = 2

In [128]: d
Out[128]: OrderedDict([('x', 1), ('y', 2), ('a', 2), ('b', 2)])

怎样实现长度为N的队列功能

In [138]: from collections import deque

In [139]: deque?
Docstring:
deque([iterable[, maxlen]]) --> deque object

Build an ordered collection with optimized access from its endpoints.
File:      /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py
Type:      type

In [141]: d = deque([], 3)

In [142]: d.append(1)

In [143]: d.append(2)

In [144]: d.append(3)

In [145]: d.append(4)

In [146]: d
Out[146]: deque([2, 3, 4])

迭代器

怎样齐头并进并行的遍历多个集合

In [147]: names = [x for x in 'abcdefg']

In [148]: ages = [x for x in range(21, 28)]

In [149]: scores = [randint(90,100) for x in range(7)]

In [150]: names
Out[150]: ['a', 'b', 'c', 'd', 'e', 'f', 'g']

In [151]: ages
Out[151]: [21, 22, 23, 24, 25, 26, 27]

In [152]: scores
Out[152]: [93, 90, 95, 97, 91, 93, 92]

In [153]:

In [153]: zip?
Docstring:
zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

Return a list of tuples, where each tuple contains the i-th element
from each of the argument sequences.  The returned list is truncated
in length to the length of the shortest argument sequence.
Type:      builtin_function_or_method

In [154]: for name,age,score in zip(names, ages, scores):
     ...:     print name,age,score
     ...:
a 21 93
b 22 90
c 23 95
d 24 97
e 25 91
f 26 93
g 27 92

怎样串行的遍历多个集合

In [158]: lista = (randint(60,70) for x in range(10))

In [159]: list(lista)
Out[159]: [65, 60, 62, 64, 63, 60, 68, 67, 61, 62]

In [160]: listb = [randint(90,100) for x in range(20)]

In [161]: listb
Out[161]:
[98,
 96,
 97,
 98,
 95,
 95,
 90,
 99,
 92,
 92,
 99,
 92,
 100,
 95,
 100,
 100,
 93,
 91,
 92,
 98]

In [163]: from itertools import chain

In [164]: chain?
Docstring:
chain(*iterables) --> chain object

Return a chain object whose .next() method returns elements from the
first iterable until it is exhausted, then elements from the next
iterable, until all of the iterables are exhausted.
File:      /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/itertools.so
Type:      type

In [165]: for x in chain(lista, listb):
     ...:     print x,
     ...:
98 96 97 98 95 95 90 99 92 92 99 92 100 95 100 100 93 91 92 98

字符串

使用多种分隔符拆分字符串

In [166]: s = 'a,b;c/d'

In [167]: import re

In [169]: re.sub?
Signature: re.sub(pattern, repl, string, count=0, flags=0)
Docstring:
Return the string obtained by replacing the leftmost
non-overlapping occurrences of the pattern in string by the
replacement repl.  repl can be either a string or a callable;
if a string, backslash escapes in it are processed.  If it is
a callable, it's passed the match object and must return
a replacement string to be used.
File:      /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.py
Type:      function

In [171]: re.sub(r'[,;/]', '-', s)
Out[171]: 'a-b-c-d'

如果进行字符串的模糊搜索与部分替换

In [172]: s = 'things happend in 2017-08-09, it is a sunddy'

In [175]: re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\2-\1-\3', s)
Out[175]: 'things happend in 08-2017-09, it is a sunddy'

列表JOIN时如果有数字元素怎么办

In [176]: print '\t'.join([str(x) for x in ['a','b',33,4.0,'e']])
a	b	33	4.0	e

文件

如何使用临时文件

In [186]: from tempfile import TemporaryFile,NamedTemporaryFile

In [187]: t = TemporaryFile()

In [188]: t.write('aa')

In [189]: t.close()

In [191]: NamedTemporaryFile?
Signature: NamedTemporaryFile(mode='w+b', bufsize=-1, suffix='', prefix='tmp', dir=None, delete=True)
Docstring:
Create and return a temporary file.
Arguments:
'prefix', 'suffix', 'dir' -- as for mkstemp.
'mode' -- the mode argument to os.fdopen (default "w+b").
'bufsize' -- the buffer size argument to os.fdopen (default -1).
'delete' -- whether the file is deleted on close (default True).
The file is created as mkstemp() would do it.

Returns an object with a file-like interface; the name of the file
is accessible as its 'name' attribute.  The file will be automatically
deleted when it is closed unless the 'delete' argument is set to False.
File:      /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/tempfile.py
Type:      function

In [192]: t = NamedTemporaryFile()

In [193]: t.write("a")

In [194]: t.name
Out[194]: '/var/folders/sc/rpg0yq054hb7vdr83ms1rp2w0000gn/T/tmpQIONuU'

并发编程

如何使用多线程

In [8]: cat multi_threading.py
from threading import Thread

def func(x):
    print x, x*x*x

ts = []
for x in range(10):
    t = Thread(target=func, args=(x,))
    t.start()
    ts.append(t)

for t in ts:
    t.join()

print 'main thread over'

In [9]: %run multi_threading.py
0 0
1 1
2 8
3 27
4 64
5 125
6 216
7 343
8 512
9 729
main thread over

上一中是直接用函数执行,第二种是先创建一个类继承自Thread类

In [18]: cat multi_threading_class.py
from threading import Thread

class MyThread(Thread):
    def __init__(self, x):
        Thread.__init__(self)
        self.x = x

    def run(self):
        print self.x, self.x*self.x*self.x


ts = []
for x in range(10):
    t = MyThread(x)
    t.start()
    ts.append(t)

for t in ts:
    t.join()

print 'main thread over'

In [19]: %run multi_threading_class.py
0 0
1 1
2 8
3 27
4 64
 5 125
6 216
7 343
8 512
9 729
main thread over

线程间通信-生产者消费者模式

In [8]: cat producer_consumer.py
#coding:utf8

from threading import Thread,currentThread
from Queue import Queue
from time import sleep
from random import randint,sample
from itertools import chain

class Producer(Thread):
    def __init__(self, queue):
        Thread.__init__(self)
        self.queue = queue

    def run(self):
       for x in range(5):
            sleep(randint(1,3))
            ele = sample('abcdefg',1)[0]
            print 'producer %s: %s'%(currentThread().name, ele)
            self.queue.put(ele)

class Consumer(Thread):
    def __init__(self, queue):
        Thread.__init__(self)
        self.setDaemon(True)
        self.queue = queue

    def run(self):
        while(True):
            e = self.queue.get()
            sleep(1)
            print 'consumer %s: %s' % (currentThread().name, e)


queue = Queue()
tps = []
for x in range(3):
    tp = Producer(queue)
    tp.start()
    tps.append(tp)

for x in range(2):
    tc = Consumer(queue)
    tc.start()

for t in tps:
    t.join()

print 'main thread over'

In [9]: %run producer_consumer.py
producer Thread-301: a
^Cconsumer Thread-304: a
producer Thread-302: c
 producer Thread-303: g
producer Thread-301: c
consumer Thread-304: g
 consumer Thread-305: c
producer Thread-303: b

 

怎样借助Python爬虫给宝宝起个好名字

2019-03-31更新:

代码的python3简化版本:

https://github.com/peiss/chinese-name-score/tree/master/chinese-name-score/python3

代码的视频讲解(我的付费视频课程中的一节):

https://www.iqiyi.com/v_19rsj1yvy8.html


每个人一生中都会遇到一件事情,在事情出现之前不会关心,但是事情一旦来临就发现它极其重要,并且需要在很短的时间内做出重大决定,那就是给自己的新生宝宝起个名字。

因为要在孩子出生后两周内起个名字(需要办理出生证明了),估计很多人都像我一样,刚开始是很慌乱的,虽然感觉汉字非常的多随便找个字做名字都行,后来才发现真不是随便的事情,怎么想都发现不合适,于是到处翻词典、网上搜、翻唐诗宋词、诗经、甚至武侠小说,然而想了很久得到的名字,往往却受到家属的意见和反对,比如不顺口、和亲戚重名重音等问题,这样就陷入了重复寻找和否定的循环,越来越混乱。

于是我们再次回到网上各种搜索,找到很多网上给出的“男宝宝好听的名字大全”之类的文章,这些文章一下子给出几百上千个名字,看的眼花缭乱没法使用。而有不少的测名字的网站或者APP,输入名字能给出八字或者五格的评分,这样的功能感觉还挺好的能给个参考,然而要么我们需要一个个名字的输入进行测试、要么这些网站或者APP自身的名字很少、要么不能满足我们的需求比如限定字、要么就开始收费,到最后也找不到一个好用的。

于是我想做这么一个程序:

  1. 主要的功能,是给出批量名字提供参考,这些名字是结合宝宝的生辰八字算出来的;
  2. 自己可以扩充名字库,比如网上发现了一批诗经里的好名字,想看看怎么样,添加进去就能用;
  3. 可以限定名字的使用字,比如有的家族谱有限定,当前是“国”字辈,名字中必须有“国”字;
  4. 名字列表可以给出评分,这样倒排后就可以从高分往低分来看名字;

通过这种方式可以得到一份符合自己孩子生辰八字、自己的家谱限制、以及自己喜好的名字列表,并且该列表已经给出了分数用于参考,以此为基准我们可以挨个琢磨找出心仪的名字。当然如果有新的想法,随时可以把新的名字添加到词库里面,进行重新计算。

程序的代码结构

代码介绍:

  • /chinese-name-score 代码根目录
  • /chinese-name-score/main 代码目录
  • /chinese-name-score/main/dicts 词典文件目录
  • /chinese-name-score/main/dicts/names_boys_double.txt 词典文件,男孩的双字名字
  • /chinese-name-score/main/dicts/names_boys_single.txt 词典文件,男孩的单字名字
  • /chinese-name-score/main/dicts/names_girls_single.txt 词典文件,女孩的双字名字
  • /chinese-name-score/main/dicts/names_grils_double.txt 词典文件,女孩的单字名字
  • /chinese-name-score/main/outputs 输出数据目录
  • /chinese-name-score/main/outputs/names_girls_source_wxy.txt 输出的示例文件
  • /chinese-name-score/main/scripts 一些对词典文件做预处理的脚本
  • /chinese-name-score/main/scripts/unique_file_lines.py 设定词典文件,对词典中的名字去重和去空白行
  • /chinese-name-score/main/sys_config.py 程序的系统配置,包含爬取得目标URL、词典文件路径
  • /chinese-name-score/main/user_config.py 程序的用户配置,包括宝宝的年月日时分性别等设定
  • /chinese-name-score/main/get_name_score.py 程序的运行入口

使用代码的方法:

  1. 如果没有限定字,就找到词典文件names_boys_double.txt和names_grils_double.txt,可以在这里添加自己找到的一些名字列表,按行分割添加在最后即可;
  2. 如果有限定字,就找到词典文件names_boys_single.txt和names_girls_single.txt,在这里添加自己预先中意的单个字列表,按行分割添加在最后即可;
  3. 打开user_config.py,进行配置,配置项见下一节;
  4. 运行脚本get_name_score.py
  5. 在outputs目录中,查看自己的产出文件,可以复制到Excel,进行排序等操作;

程序的配置入口

程序的配置如下:

# coding:GB18030

"""
在这里写好配置
"""

setting = {}

# 限定字,如果配置了该值,则会取用单字字典,否则取用多字字典
setting["limit_world"] = "国"
# 姓
setting["name_prefix"] = "李"
# 性别,取值为 男 或者 女
setting["sex"] = "男"
# 省份
setting["area_province"] = "北京"
# 城市
setting["area_region"] = "海淀"
# 出生的公历年份
setting['year'] = "2017"
# 出生的公历月份
setting['month'] = "1"
# 出生的公历日子
setting['day'] = "11"
# 出生的公历小时
setting['hour'] = "11"
# 出生的公历分钟
setting['minute'] = "11"
# 结果产出文件名称
setting['output_fname'] = "names_girls_source_xxx.txt"

根据配置项setting[“limit_world”],系统自动来决定选用单字词典还是多字词典:

  • 如果设置了该项,比如等于“国”,那么程序会组合所有的单字为名字用于计算,比如国浩和浩国两个名字都会计算;
  • 如果不设置该项,保持空字符串,则程序只会读取*_double.txt的双字词典

程序的原理

这是一个简单的爬虫。大家可以打开http://life.httpcn.com/xingming.asp网站查看,这是一个POST表单,填写需要的参数,点提交,就会打开一个结果页面,结果页面的最下方包含了八字分数和五格分数。

如果想得到分数,就需要做两件事情,一是爬虫自动提交表单,获取结果页面;二是从结果页面提取分数;

对于第一件事情,很简单,urllib2即可实现(代码在/chinese-name-score/main/get_name_score.py):

    post_data = urllib.urlencode(params)
    req = urllib2.urlopen(sys_config.REQUEST_URL, post_data)
    content = req.read()

这里的params是个参数dict,使用这种方式,就进行了POST带数据的提交,然后从content得到了结果数据。

params的参数设定如下:

    params = {}
    
    # 日期类型,0表示公历,1表示农历
    params['data_type'] = "0"
    params['year'] = "%s" % str(user_config.setting["year"])
    params['month'] = "%s" % str(user_config.setting["month"])
    params['day'] = "%s" % str(user_config.setting["day"])
    params['hour'] = "%s" % str(user_config.setting["hour"])
    params['minute'] = "%s" % str(user_config.setting["minute"])
    params['pid'] = "%s" % str(user_config.setting["area_province"])
    params['cid'] = "%s" % str(user_config.setting["area_region"])
    # 喜用五行,0表示自动分析,1表示自定喜用神
    params['wxxy'] = "0"
    params['xing'] = "%s" % (user_config.setting["name_prefix"])
    params['ming'] = name_postfix
    # 表示女,1表示男
    if user_config.setting["sex"] == "男":
        params['sex'] = "1"
    else:
        params['sex'] = "0"
        
    params['act'] = "submit"
    params['isbz'] = "1"

第二件事情,就是从网页中提取需要的分数,我们可以使用BeautifulSoup4来实现,其语法也很简单:

    soup = BeautifulSoup(content, 'html.parser', from_encoding="GB18030")
    full_name = get_full_name(name_postfix)
    
    # print soup.find(string=re.compile(u"姓名五格评分"))
    for node in soup.find_all("div", class_="chaxun_b"):
        node_cont = node.get_text()
        if u'姓名五格评分' in node_cont:
            name_wuge = node.find(string=re.compile(u"姓名五格评分"))
            result_data['wuge_score'] = name_wuge.next_sibling.b.get_text()
        
        if u'姓名八字评分' in node_cont:
            name_wuge = node.find(string=re.compile(u"姓名八字评分"))
            result_data['bazi_score'] = name_wuge.next_sibling.b.get_text()

通过该方法,就能对HTML解析,提取八字和五格的分数。

运行结果事例

1/1287 李国锦	姓名八字评分=61.5	姓名五格评分=78.6	总分=140.1
2/1287 李国铁	姓名八字评分=61	姓名五格评分=89.7	总分=150.7
3/1287 李国晶	姓名八字评分=21	姓名五格评分=81.6	总分=102.6
4/1287 李鸣国	姓名八字评分=21	姓名五格评分=90.3	总分=111.3
5/1287 李柔国	姓名八字评分=64	姓名五格评分=78.3	总分=142.3
6/1287 李国经	姓名八字评分=21	姓名五格评分=89.8	总分=110.8
7/1287 李国蒂	姓名八字评分=22	姓名五格评分=87.2	总分=109.2
8/1287 李国登	姓名八字评分=21	姓名五格评分=81.6	总分=102.6
9/1287 李略国	姓名八字评分=21	姓名五格评分=83.7	总分=104.7
10/1287 李国添	姓名八字评分=21	姓名五格评分=81.6	总分=102.6
11/1287 李国天	姓名八字评分=22	姓名五格评分=83.7	总分=105.7
12/1287 李国田	姓名八字评分=22	姓名五格评分=93.7	总分=115.7

有了这些分数,我们就可以进行排序,是一个很实用的参考资料。

友情提示

  1. 分数跟很多因素有关,比如出生时刻、已经限定的字、限定字的笔画等因素,这些条件决定了有些名字不会分数高,不要受此影响,找出相对分数高的就可以了;
  2. 目前程序只能抓取一个网站的内容,地址是http://life.httpcn.com/xingming.asp
  3. 本列表仅供参考,看过一些文章,历史上很多名人伟人,姓名八字评分都非常低但是都建功立业,名字确实会有些影响但有时候朗朗上口就是最好的;
  4. 从本列表中选取名字之后,可以在百度、人人网等地方查查,以防有些负面的人重名、或者起这个名字的人太多了烂大街;
  5. 八字分数是中国传承,五格分数是日本人近代发明的,有时候也可以试试西方的星座起名法,并且奇怪的是八字和五个分数不同网站打分相差很大,更说明了这东西只供参考;

本文的代码已上传到github:https://github.com/peiss/chinese-name-score

附注:有些朋友不会运行程序,但是想得到宝宝起名结果列表,可以QQ693609645联系我,我帮你运行一份结果,收取少量费用

本文地址:http://crazyant.net/2076.html,转载请注明来源。

 

Python使用unittest实现简单的单元测试实例

如果项目复杂,进行单元测试是保证降低出错率的好方法,Python提供的unittest可以很方便的实现单元测试,从而可以替换掉繁琐杂乱的main函数测试的方法,将测试用例、测试方法进行统一的管理和维护。

本文给出一个实例,很简单,看一下就明白了。

首先给出一个要测试的Python模块,代码如下:

# coding:utf8
'''
日期常用类

@author: www.crazyant.net
'''

def get_date_year_month(pm_date):
    """获取参数pm_date对应的年份和月份
    """
    if not pm_date:
        raise Exception("get_curr_year_month: pm_date can not be None")
    
    # get date's yyyymmddHHMMSS pattern
    str_date = str(pm_date).replace("-", "").replace(" ", "").replace(":", "")
    
    year = str_date[:4]
    month = str_date[4:6]
    return year, month

然后就可以编写测试脚本,代码如下:

# coding: utf8

"""
测试date_service.py

@author: peishuaishuai
"""

import unittest

from service import date_service

class DateServiceTest(unittest.TestCase):
    """
    test clean_tb_async_src_acct.py
    """
    
    def setup(self):
        """在这里做资源的初始化 """
        pass
    
    def tearDown(self):
        """在这里做资源的释放 """
        pass
    
    def test_get_date_year_month_1(self):
        """ 测试方法1,测试方法应该以test_开头 """
        
        pm_date = "2015-11-25 14:40:52"
        year, month = date_service.get_date_year_month(pm_date)
        self.assertEqual(year, "2015", "year not equal")
        self.assertEqual(month, "11", "month not equal")
    
    def test_get_date_year_month_2(self):
        """ 测试方法1,测试方法应该以test_开头 """
        pm_date = "20161225144052"
        year, month = date_service.get_date_year_month(pm_date)
        self.assertEqual(year, "2016", "year not equal")
        self.assertEqual(month, "12", "month not equal")


# test main
if __name__ == "__main__":
    unittest.main()

运行这个test_date_service.py,就会打印出如下信息:

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

这里的每一个点,就代表运行成功了一个测试,最后会给出运行成功了全部的多少个测试以及测试的时间。

之前的很多时间,我一直不知道写单测有什么用,因为单测只是把写好的程序运行了一遍,并没有创建新的逻辑,我心里在疑惑“我已经将程序按照我的想法写好了,它就会按照我的设计来运行,为什么要用单测重新走一遍呢?”,后来出了一个事情,代码出了BUG,我调试了好久,才发现问题出在”obja.equals(objb)”,因为obja和objb一个是Long一个是Integer,所以即使数值相同,也不会相等。

从那一刻,我发现单测做的事情,其实就是“验证程序是否按照我的想法在运行”,这才是它的终极目的,但是,这却是很关键的事情,设计往往没有错,但是写出来的代码却经常并不是按照我们所想的去运行的。

单测,就是验证代码是不是按照我们想象的在运行,这也是单测这个技术的意义所在。

本文地址:http://crazyant.net/1890.html,转载请注明来源。