添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

之前开发了一个工具包 GerapyPyppeteer,GitHub 地址为 https://github.com/Gerapy/GerapyPyppeteer,这个包实现了 Scrapy 和 Pyppeteer 的对接,利用它我们就可以方便地实现 Scrapy 使用 Pyppeteer 爬取动态渲染的页面了。 另外,很多朋友在运行爬虫的时候可能会使用到 Docker,想把 Scrapy 和 Pyppeteer 打包成 Docker 运行,但是这个打包和测试过程中大家可能会遇到一些问题,在这里对 Pyppeteer 打包 Docker 的坑简单做一下总结。

Pyppeteer 打包 Docker 主要是有这么几个坑点:

  • 依赖没有安装,导致无法正确安装和启动 Pyppeteer。
  • 没有关闭沙盒模式,导致可能出现 Browser closed unexpectedly 错误
  • 没有提前安装好 Pyppeteer,导致每次启动时都要重新安装
  • 下面我们分别对三个问题做下简单的 Troubleshooting。

    首先说第一个,安装依赖。 因为 Docker 大部分都是基于 Linux 系统的,比如我常用的基础镜像就是 python:3.7 ,剩余 Debian 系列,当然还有很多其他的版本,具体可以查看 https://hub.docker.com/_/python 了解下。 但是对于 Pyppeteer 来说, python:3.7 内置的依赖库并不够,我们还需要额外进行安装,安装完毕之后还需要清空下 apt list,一句 Dockerfile 命令如下:

    RUN apt-get update && 
    apt-get -y install libnss3 xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2
    libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0
    libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1
    libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1
    libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget &&
    rm -rf /var/lib/apt/lists/*

    这里是提前安装了一下 Pyppteer 这个 Python 库,然后利用 Python 库提供的 pyppeteer-install 命令提前下载了 Chromium 浏览器。 这样后面启动的时候就可以直接唤起 Chromium 浏览器进行爬取了。

    最后看下完整 Dockerfile,内容如下:

    FROM python:3.7

    RUN apt-get update &&
    apt-get -y install libnss3 xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2
    libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0
    libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1
    libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1
    libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget &&
    rm -rf /var/lib/apt/lists/*

    RUN pip install -U pip && pip install pyppeteer && pyppeteer-install

    WORKDIR /code
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY . .
    CMD python3 run.py

    解析页面是做爬虫的过程中的重要环节,而且如果站点多了,解析也会变得非常复杂,所以智能化解析就可能是一个不错的解决方案。如果我们能够容忍一定的错误率,那么我们可以利用智能化解析算法帮我们提取一些内容,简单高效。 那有没有办法做到一个网站的全自动化解析呢? 比如来了一个博客网站,我能首先识别出来这是一个列表页还是文章(详情)页,然后提取列表页的每篇文章的链接,然后跳转到每篇文章(详情)页再提取文章相关信息。 那么这里面可能就有四个关键部分:

  • 判断当前所在的页面是列表页还是文章(详情)页
  • 识别出列表页下一页的链接
  • 识别出列表页所有列表链接
  • 识别出文章(详情)页的文章内容和其他信息
  • 如果我们能把这四步都用算法实现出来,那么我们只需要一个网站的主站链接就能轻松地把内容规整地爬取下来了。 那么这篇文章我们就来简单说下第一步,如何判断当前所在的页面的列表页还是文章(详情)页。

    注:后文中文章页统一称之为详情页。

    列表页和详情页不知道大家有没有基本的概念了,列表页就是导航页,里面带有好多文章或新闻或详情链接,我们选一个链接点进去就是详情页。 比如说这里拿新浪体育来说,首页如图所示: image-20200720193407237 看到这里面有很多链接,就是一些页面导航集合,这个页面就是列表页。 然后我们随便点开一篇新闻报道,如图所示: image-20200720193504042 这里就是一篇新闻报道,带有醒目的标题、发布时间、正文等内容,这就是详情页。 现在我们要做的就是用一个算法来凭借 HTML 代码区分出来哪个是列表页,哪个是详情页。 最后的输入输出如下:

  • 输入:一个页面的 HTML 代码
  • 输出:这个页面是列表页还是详情页,并输出二者的判定概率。
  • 首先我们确认下这个问题是个什么问题。 很明显,结果要么是列表页,要么是详情页,就是个二分类问题。 那二分类问题怎么解决呢?实现一个基本的分类模型就好了。大范围就是传统机器学习和现在比较流行的深度学习。总体上来说,深度学习的精度和处理能力会强一点,但是想想我们现在的应用场景,后者要追求精度的话可能需要更多的标注数据,而前者也有比较不错的易用的模型了,比如 SVM。 所以,我们不妨先选用 SVM 模型来实现一个基本的二分类模型来试试看,效果如果已经很好了或者提升空间不大了,那就直接用就好了,如果效果比较差,那我们再选用其他模型来优化。 好,那就定下来了,我们用 SVM 模型来实现一下试试。

    既然要做分类模型,那么最重要的当然就是数据标注了,我们分两组就好了,一组是列表页,一组是详情页,我们先用手工配合爬虫找一些列表页和详情页的 HTML 代码,然后将其保存下来。 结果类似如下: image-20200720195132784 每个文件夹几百个就行了,数量不用太多,五花八门的页面混起来更好。

    既然要做 SVM,那么我们得想清楚要分清两个类别需要哪些特征。既然是特征,那我们就要选出二者不同的特征,这样更加有区分度。 比如这里我大体总结了有这么几个特征:

  • 文本密度:正文页通常会包含密集的文字,比如一个 p 节点内部就包含几十上百个文字,如果用单个节点内的文字数目来表示文本密度的话,那么详情页的部分内容文本密度会很高。
  • 超链接节点的数量和比例:一般来说列表页通常会包含多个超链接,而且很大比例都是超链接文本,而详情页却有很多的文字并不是超链接,比如正文。
  • 符号密度:一般来说列表页通常会是一些标题导航,一般可能都不会包含句号,而详情页的正文内容通常就会包含句号等内容,如果按照单位文字所包含的标点符号数量来表示符号密度的话,后者的符号密度也会高一些。
  • 列表簇的数目:一般来说,列表页通常会包含多个具有公共父节点的条目,多个条目构成一个列表簇,虽然说详情页侧栏也会包含一些列表,但至少这个数量也可以成为一个特征来判别。
  • meta 信息:有一些特殊的 meta 信息是列表页独有的,比如只有详情页才会有发布时间,而列表页通常是没有的。
  • 正文标题和 title 标题相似度:一般来说,详情页的正文标题和 title 标题很可能是相同的内容,而列表页通常则是站点的名称。
  • 以上便是我简单列的几个特征,还有很多其他的特征也可以来挖掘,比如视觉特征等等。

    真正代码实现的过程就是将现有的 HTML 文本进行预处理,把上面的一些特征提取出来,然后直接声明一个 SVM 分类模型即可。 这里声明了一个 feature 名字和对应的处理方法:

    self.feature_funcs = {
    'number_of_a_char': number_of_a_char,
    'number_of_a_char_log10': self._number_of_a_char_log10,
    'number_of_char': number_of_char,
    'number_of_char_log10': self._number_of_char_log10,
    'rate_of_a_char': self._rate_of_a_char,
    'number_of_p_descendants': number_of_p_descendants,
    'number_of_a_descendants': number_of_a_descendants,
    'number_of_punctuation': number_of_punctuation,
    'density_of_punctuation': density_of_punctuation,
    'number_of_clusters': self._number_of_clusters,
    'density_of_text': density_of_text,
    'max_density_of_text': self._max_density_of_text,
    'max_number_of_p_children': self._max_number_of_p_children,
    'has_datetime_meta': self._has_datetime_mata,
    'similarity_of_title': self._similarity_of_title,
    }
    self.feature_names = self.feature_funcs.keys()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    list_file_paths = list(glob(f'{DATASETS_LIST_DIR}/*.html'))
    detail_file_paths = list(glob(f'{DATASETS_DETAIL_DIR}/*.html'))

    x_data, y_data = [], []

    for index, list_file_path in enumerate(list_file_paths):
    logger.log('inspect', f'list_file_path {list_file_path}')
    element = file2element(list_file_path)
    if element is None:
    continue
    preprocess4list_classifier(element)
    x = self.features_to_list(self.features(element))
    x_data.append(x)
    y_data.append(1)

    for index, detail_file_path in enumerate(detail_file_paths):
    logger.log('inspect', f'detail_file_path {detail_file_path}')
    element = file2element(detail_file_path)
    if element is None:
    continue
    preprocess4list_classifier(element)
    x = self.features_to_list(self.features(element))
    x_data.append(x)
    y_data.append(0)

    # preprocess data
    ss = StandardScaler()
    x_data = ss.fit_transform(x_data)
    joblib.dump(ss, self.scaler_path)
    x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size=0.2, random_state=5)

    # set up grid search
    c_range = np.logspace(-5, 20, 5, base=2)
    gamma_range = np.logspace(-9, 10, 5, base=2)
    param_grid = [
    {'kernel': ['rbf'], 'C': c_range, 'gamma': gamma_range},
    {'kernel': ['linear'], 'C': c_range},
    ]
    grid = GridSearchCV(SVC(probability=True), param_grid, cv=5, verbose=10, n_jobs=-1)
    clf = grid.fit(x_train, y_train)
    y_true, y_pred = y_test, clf.predict(x_test)
    logger.log('inspect', f'n{classification_report(y_true, y_pred)}')
    score = grid.score(x_test, y_test)
    logger.log('inspect', f'test accuracy {score}')
    # save model
    joblib.dump(grid.best_estimator_, self.model_path)

    这里首先对数据进行预处理,然后将每个 feature 存 存到 x_data 中,标注结果存到 y_data 中。接着我们使用 StandardScaler 对数据进行标准化处理,然后进行随机切分。最后使用 GridSearch 训练了一个 SVM 模型然后保存了下来。 以上便是基本的模型训练过程,具体的代码可以再完善一下。

    以上的流程我已经实现了,并且发布了一个开源 Python 包,名字叫做 Gerapy AutoExtractor,GitHub 地址为 https://github.com/Gerapy/GerapyAutoExtractor 。 大家如需使用可以使用 pip 安装:

    from gerapy_auto_extractor import is_detail, is_list, probability_of_detail, probability_of_list
    from gerapy_auto_extractor.helpers import content, jsonify

    html = content('detail.html')
    print(probability_of_detail(html), probability_of_list(html))
    print(is_detail(html), is_list(html))

    html = content('list.html')
    print(probability_of_detail(html), probability_of_list(html))
    print(is_detail(html), is_list(html))
    def init_device(platform="Android", uuid=None, **kwargs):
    """
    Initialize device if not yet, and set as current device.

    :param platform: Android, IOS or Windows
    :param uuid: uuid for target device, e.g. serialno for Android, handle for Windows, uuid for iOS
    :param kwargs: Optional platform specific keyword args, e.g. `cap_method=JAVACAP` for Android
    :return: device instance
    """

    可以发现它返回的是一个 Android 对象。 这个 Android 对象实际上属于 airtest.core.android 这个包,继承自 airtest.core.device.Device 这个类,与之并列的还有 airtest.core.ios.ios.IOS airtest.core.linux.linux.Linux airtest.core.win.win.Windows 等。这些都有一些针对 Device 操作的 API,下面我们以 airtest.core.android.android.Android 为例来总结一下。

  • get_default_device:获取默认 device
  • uuid:获取当前 Device 的 UUID
  • list_app:列举所有 App
  • path_app:打印输出某个 App 的完整路径
  • check_app:检查某个 App 是否在当前设备上
  • start_app:启动某个 App
  • start_app_timing:启动某个 App,然后计算时间
  • stop_app:停止某个 App
  • clear_app:清空某个 App 的全部数据
  • install_app:安装某个 App
  • install_multiple_app:安装多个 App
  • uninstall_app:卸载某个 App
  • snapshot:屏幕截图
  • shell:获取 Adb Shell 执行的结果
  • keyevent:执行键盘操作
  • wake:唤醒当前设备
  • home:点击 HOME 键
  • text:向设备输入内容
  • touch:点击屏幕某处的位置
  • double_click:双击屏幕某处的位置
  • swipe:滑动屏幕,由一点到另外一点
  • pinch:手指捏和操作
  • logcat:日志记录操作
  • getprop:获取某个特定属性的值
  • get_ip_address:获取 IP 地址
  • get_top_activity:获取当前 Activity
  • get_top_activity_name_and_pid:获取当前 Activity 的名称和进程号
  • get_top_activity_name:获取当前 Activity 的名称
  • is_keyboard_shown:判断当前键盘是否出现了
  • is_locked:设备是否锁定了
  • unlock:解锁设备
  • display_info:获取当前显示信息,如屏幕宽高等
  • get_display_info:同 display_info
  • get_current_resolution:获取当前设备分辨率
  • get_render_resolution:获取当前渲染分辨率
  • start_recording:开始录制
  • stop_recording:结束录制
  • adjust_all_screen:调整屏幕适配分辨率
  • 了解了上面的方法之后,我们可以用一个实例来感受下它们的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    from airtest.core.android import Android
    from airtest.core.api import *
    import logging

    logging.getLogger("airtest").setLevel(logging.WARNING)

    device: Android = init_device('Android')
    is_locked = device.is_locked()
    print(f'is_locked: {is_locked}')

    if is_locked:
    device.unlock()

    device.wake()

    app_list = device.list_app()
    print(f'app list {app_list}')

    uuid = device.uuid
    print(f'uuid {uuid}')

    display_info = device.get_display_info()
    print(f'display info {display_info}')

    resolution = device.get_render_resolution()
    print(f'resolution {resolution}')

    ip_address = device.get_ip_address()
    print(f'ip address {ip_address}')

    top_activity = device.get_top_activity()
    print(f'top activity {top_activity}')

    is_keyboard_shown = device.is_keyboard_shown()
    print(f'is keyboard shown {is_keyboard_shown}')
    is_locked: False
    app list ['com.kimcy929.screenrecorder', 'com.android.providers.telephony', 'io.appium.settings', 'com.android.providers.calendar', 'com.android.providers.media', 'com.goldze.mvvmhabit', 'com.android.wallpapercropper', 'com.android.documentsui', 'com.android.galaxy4', 'com.android.externalstorage', 'com.android.htmlviewer', 'com.android.quicksearchbox', 'com.android.mms.service', 'com.android.providers.downloads', 'mark.qrcode', ..., 'com.google.android.play.games', 'io.kkzs', 'tv.danmaku.bili', 'com.android.captiveportallogin']
    uuid emulator-5554
    display info {'id': 0, 'width': 1080, 'height': 1920, 'xdpi': 320.0, 'ydpi': 320.0, 'size': 6.88, 'density': 2.0, 'fps': 60.0, 'secure': True, 'rotation': 0, 'orientation': 0.0, 'physical_width': 1080, 'physical_height': 1920}
    resolution (0.0, 0.0, 1080.0, 1920.0)
    ip address 10.0.2.15
    top activity ('com.microsoft.launcher.dev', 'com.microsoft.launcher.Launcher', '16040')
    is keyboard shown False
    def connect_device(uri):
    """
    Initialize device with uri, and set as current device.

    :param uri: an URI where to connect to device, e.g. `android://adbhost:adbport/serialno?param=value&param2=value2`
    :return: device instance
    :Example:
    * ``android:///`` # local adb device using default params
    * ``android://adbhost:adbport/1234566?cap_method=javacap&touch_method=adb`` # remote device using custom params
    * ``windows:///`` # local Windows application
    * ``ios:///`` # iOS device
    """

    这里需要注意的是,在最开始没有调用 connect_device 方法之前,DEVICE_LIST 是空的,在调用之后 DEVICE_LIST 会自动添加已经连接的 device,DEVICE_LIST 就是已经连接的 device 列表

    切换 device

    我们可以使用 set_current 方法切换当前连接的 device,传入的是 index,定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    MemTotal:        3627908 kB
    MemFree: 2655560 kB
    MemAvailable: 2725928 kB
    Buffers: 3496 kB
    Cached: 147472 kB
    SwapCached: 0 kB
    Active: 744592 kB
    Inactive: 126332 kB
    Active(anon): 723292 kB
    Inactive(anon): 16344 kB
    Active(file): 21300 kB
    Inactive(file): 109988 kB
    Unevictable: 0 kB
    Mlocked: 0 kB
    HighTotal: 2760648 kB
    HighFree: 2073440 kB
    LowTotal: 867260 kB
    LowFree: 582120 kB
    SwapTotal: 0 kB
    SwapFree: 0 kB
    Dirty: 0 kB
    Writeback: 0 kB
    AnonPages: 720100 kB
    Mapped: 127720 kB
    Shmem: 19428 kB
    Slab: 76196 kB
    SReclaimable: 7392 kB
    SUnreclaim: 68804 kB
    KernelStack: 7896 kB
    PageTables: 8544 kB
    NFS_Unstable: 0 kB
    Bounce: 0 kB
    WritebackTmp: 0 kB
    CommitLimit: 1813952 kB
    Committed_AS: 21521776 kB
    VmallocTotal: 122880 kB
    VmallocUsed: 38876 kB
    VmallocChunk: 15068 kB
    DirectMap4k: 16376 kB
    DirectMap4M: 892928 kB
    @logwrap
    def start_app(package, activity=None):
    """
    Start the target application on device

    :param package: name of the package to be started, e.g. "com.netease.my"
    :param activity: the activity to start, default is None which means the main activity
    :return: None
    :platforms: Android, iOS
    """
    G.DEVICE.start_app(package, activity)

    @logwrap
    def stop_app(package):
    """
    Stop the target application on device

    :param package: name of the package to stop, see also `start_app`
    :return: None
    :platforms: Android, iOS
    """
    G.DEVICE.stop_app(package)
    @logwrap
    def install(filepath, **kwargs):
    """
    Install application on device

    :param filepath: the path to file to be installed on target device
    :param kwargs: platform specific `kwargs`, please refer to corresponding docs
    :return: None
    :platforms: Android
    """
    return G.DEVICE.install_app(filepath, **kwargs)

    @logwrap
    def uninstall(package):
    """
    Uninstall application on device

    :param package: name of the package, see also `start_app`
    :return: None
    :platforms: Android
    """
    return G.DEVICE.uninstall_app(package)
    def snapshot(filename=None, msg="", quality=ST.SNAPSHOT_QUALITY):
    """
    Take the screenshot of the target device and save it to the file.

    :param filename: name of the file where to save the screenshot. If the relative path is provided, the default
    location is ``ST.LOG_DIR``
    :param msg: short description for screenshot, it will be recorded in the report
    :param quality: The image quality, integer in range [1, 99]
    :return: absolute path of the screenshot
    :platforms: Android, iOS, Windows
    """
    @logwrap
    def touch(v, times=1, **kwargs):
    """
    Perform the touch action on the device screen

    :param v: target to touch, either a Template instance or absolute coordinates (x, y)
    :param times: how many touches to be performed
    :param kwargs: platform specific `kwargs`, please refer to corresponding docs
    :return: finial position to be clicked
    :platforms: Android, Windows, iOS
    """

    另外上述的 touch 方法还可以完全等同于 click 方法。 如果要双击的话,还可以使用调用 double_click 方法,传入参数也可以是 Template 或者绝对位置。

    滑动可以使用 swipe 方法,可以传入起始和终止位置,两个位置都可以传入绝对位置或者 Template,定义如下:

    @logwrap
    def swipe(v1, v2=None, vector=None, **kwargs):
    """
    Perform the swipe action on the device screen.

    There are two ways of assigning the parameters
    * ``swipe(v1, v2=Template(...))`` # swipe from v1 to v2
    * ``swipe(v1, vector=(x, y))`` # swipe starts at v1 and moves along the vector.

    :param v1: the start point of swipe,
    either a Template instance or absolute coordinates (x, y)
    :param v2: the end point of swipe,
    either a Template instance or absolute coordinates (x, y)
    :param vector: a vector coordinates of swipe action, either absolute coordinates (x, y) or percentage of
    screen e.g.(0.5, 0.5)
    :param **kwargs: platform specific `kwargs`, please refer to corresponding docs
    :raise Exception: general exception when not enough parameters to perform swap action have been provided
    :return: Origin position and target position
    :platforms: Android, Windows, iOS
    """
    @logwrap
    def pinch(in_or_out='in', center=None, percent=0.5):
    """
    Perform the pinch action on the device screen

    :param in_or_out: pinch in or pinch out, enum in ["in", "out"]
    :param center: center of pinch action, default as None which is the center of the screen
    :param percent: percentage of the screen of pinch action, default is 0.5
    :return: None
    :platforms: Android
    """
    @logwrap
    def text(text, enter=True, **kwargs):
    """
    Input text on the target device. Text input widget must be active first.

    :param text: text to input, unicode is supported
    :param enter: input `Enter` keyevent after text input, default is True
    :return: None
    :platforms: Android, Windows, iOS
    """
    @logwrap
    def wait(v, timeout=None, interval=0.5, intervalfunc=None):
    """
    Wait to match the Template on the device screen

    :param v: target object to wait for, Template instance
    :param timeout: time interval to wait for the match, default is None which is ``ST.FIND_TIMEOUT``
    :param interval: time interval in seconds to attempt to find a match
    :param intervalfunc: called after each unsuccessful attempt to find the corresponding match
    :raise TargetNotFoundError: raised if target is not found after the time limit expired
    :return: coordinates of the matched target
    :platforms: Android, Windows, iOS
    """
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    @logwrap
    def assert_exists(v, msg=""):
    """
    Assert target exists on device screen

    :param v: target to be checked
    :param msg: short description of assertion, it will be recorded in the report
    :raise AssertionError: if assertion fails
    :return: coordinates of the target
    :platforms: Android, Windows, iOS
    """
    try:
    pos = loop_find(v, timeout=ST.FIND_TIMEOUT, threshold=ST.THRESHOLD_STRICT)
    return pos
    except TargetNotFoundError:
    raise AssertionError("%s does not exist in screen, message: %s" % (v, msg))

    @logwrap
    def assert_not_exists(v, msg=""):
    """
    Assert target does not exist on device screen

    :param v: target to be checked
    :param msg: short description of assertion, it will be recorded in the report
    :raise AssertionError: if assertion fails
    :return: None.
    :platforms: Android, Windows, iOS
    """
    try:
    pos = loop_find(v, timeout=ST.FIND_TIMEOUT_TMP)
    raise AssertionError("%s exists unexpectedly at pos: %s, message: %s" % (v, pos, msg))
    except TargetNotFoundError:
    pass

    @logwrap
    def assert_equal(first, second, msg=""):
    """
    Assert two values are equal

    :param first: first value
    :param second: second value
    :param msg: short description of assertion, it will be recorded in the report
    :raise AssertionError: if assertion fails
    :return: None
    :platforms: Android, Windows, iOS
    """
    if first != second:
    raise AssertionError("%s and %s are not equal, message: %s" % (first, second, msg))

    @logwrap
    def assert_not_equal(first, second, msg=""):
    """
    Assert two values are not equal

    :param first: first value
    :param second: second value
    :param msg: short description of assertion, it will be recorded in the report
    :raise AssertionError: if assertion
    :return: None
    :platforms: Android, Windows, iOS
    """
    if first == second:
    raise AssertionError("%s and %s are equal, message: %s" % (first, second, msg))

    这几个断言比较常用的就是 assert_exists 和 assert_not_exists 判断某个目标是否存在于屏幕上,同时还可以传入 msg,它可以被记录到 report 里面。 以上就是 Airtest 的 API 的用法,它提供了一些便捷的方法封装,同时还对接了图像识别等技术。 但 Airtest 也有一定的局限性,比如不能根据 DOM 树来选择对应的节点,依靠图像识别也有一定的不精确之处,所以还需要另外一个库 —— Poco。

    利用 Poco 我们可以支持 DOM 选择,例如编写 XPath 等来定位某一个节点。 首先需要安装 Poco,使用 pip3 即可:

    from airtest.core.api import *
    from poco.drivers.android.uiautomation import AndroidUiautomationPoco
    from poco.proxy import UIObjectProxy

    poco = AndroidUiautomationPoco()

    uri = 'Android://127.0.0.1:5037/emulator-5554'
    connect_device(uri)
    home()
    object: UIObjectProxy = poco("com.microsoft.launcher.dev:id/workspace")
    print(object)

    poco 返回的是 UIObjectProxy 对象,它提供了其他的操作 API,例如选取子节点,兄弟节点,父节点等等,同时可以调用各个操作方法,如 click、pinch、scroll 等等。 具体的操作文档可以参见: https://poco.readthedocs.io/en/latest/source/poco.proxy.html 下面简单总结:

  • attr:获取节点属性
  • child:获取子节点
  • children:获取所有子节点
  • click:点击
  • double_click:双击
  • drag_to:将某个节点拖拽到另一个节点
  • exists:某个节点是否存在
  • focus:获得焦点
  • get_bounds:获取边界
  • get_name:获取节点名
  • get_position:获取节点位置
  • get_size:获取节点大小
  • get_text:获取文本内容
  • long_click:长按
  • offspring:选出包含直接子代的后代
  • parent:父节点
  • pinch:缩放
  • scroll:滑动
  • set_text:设置文字
  • sibling:兄弟节点
  • swipe:滑动
  • wait:等待
  • wait_for_appearance:等待某个节点出现
  • wait_for_disappearance:等待某个节点消失
  • 以上的这些方法混用的话就可以执行各种节点的选择和相应的操作。

    最近遇到一个问题,那就是需要给别人共享一下 Kubernetes 的某个资源的使用和访问权限,这个仅仅存在于某个 namespace 下,但是我又不能把管理员权限全都给它,我想只给他授予这一个 Namespace 下的权限,那应该怎么办呢? 比如我这边是需要只想授予 postgresql 这个 Namespace 的权限,这里我就需要利用到 Kubernetes 里面的 RBAC 机制来实现了,下面记录了我的操作流程。

    创建 Namespace

    这里由于 Role 是 Namespace 级别的,所以只能在特定 Namespace 下生效,这里我要让授予本 Namespace 下的所有权限,这里 rules 就添加了所有的 API类型、资源类型和操作类型。

    创建 RoleBinding

    最后需要将 Role 和 ServiceAccount 绑定起来,yaml 如下:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
    annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
    {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"postgresql","namespace":"postgresql"}}
    creationTimestamp: "2020-07-30T16:10:38Z"
    name: postgresql
    namespace: postgresql
    resourceVersion: "17800240"
    selfLink: /api/v1/namespaces/postgresql/serviceaccounts/postgresql
    uid: 6327db1f-6a93-4f1e-b988-31842989bbbc
    secrets:
    - name: postgresql-token-v26k7
    server=https://your-server:443
    name=postgresql-token-v26k7
    namespace=postgresql

    ca=$(kubectl get secret/$name -n $namespace -o jsonpath='{.data.ca.crt}')
    token=$(kubectl get secret/$name -n $namespace -o jsonpath='{.data.token}' | base64 --decode)

    echo "apiVersion: v1
    kind: Config
    clusters:
    - name: test
    cluster:
    certificate-authority-data: ${ca}
    server: ${server}
    contexts:
    - name: test
    context:
    cluster: test
    user: postgresql
    current-context: test
    users:
    - name: postgresql
    user:
    token: ${token}
    " > postgresql.kubeconfig
  • server:就是 Kubernetes Server API 的地址
  • name:就是 ServiceAccount 对应的 secret
  • namespace:就是当前操作的 Namespace
  • 运行之后就会生成一个 portgresql.kubeconfig 文件。

    那么怎么使用呢?很简单,设置下环境变量切换下就好了。

    NAME                  TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    postgresql ClusterIP 10.0.193.137 <none> 5432/TCP 9d
    postgresql-headless ClusterIP None <none> 5432/TCP 9d
    postgresql-metrics ClusterIP 10.0.60.88 <none> 9187/TCP 9d
    postgresql-read ClusterIP 10.0.236.184 <none> 5432/TCP 9d

    之前也写过不少关于爬虫的博客了,比如我拿一个案例来写了一篇博客,当时写的时候好好的,结果过了一段时间这个页面改版了,甚至直接下线了,那这篇案例就废掉了。 另外如果拿别人的站或者 App 来做案例的话,比较容易触犯到对方的利益,风险比较高,比如把某个站的 JavaScript 逆向方案公布出来,比如把某个 App 的逆向方案公布出来。如果此时此刻没有对方联系你的话,一个很大原因可能是规模太小了别人没注意到,但不代表以后不会。我还是选择爱护自己的羽毛,关于逆向实际网站和 App 的案例我都不会发的。在这种情况下比较理想的方案是自建案例,只用这个案例讲明白对应的知识点就可以了。 所以,为此,这段时间我也在写一些爬虫相关的案例,比如:

  • 无反爬的服务端渲染网站
  • 带有参数加密的 SPA 网站
  • 各类形式验证码网站
  • 反 WebDriver 网站
  • 字体反爬等网站
  • 模拟登录网站
  • App 案例,如代理检测,SSL Pining 等
  • 今天发布一下。

    本案例平台自爬数据、自建页面、自接反爬,案例稳定后永不过期,适合教学与练习。

    SSR 网站

  • ssr1 :猫眼电影数据网站,数据通过服务端渲染,适合基本爬虫练习。
  • ssr2 :HTTPS 无效证书网站,适合做跳过验证 HTTPS 案例。
  • ssr3 :HTTP Basic Authentication 网站,适合做 HTTP 认证案例,用户名密码均为 admin。
  • ssr4 :每个响应增加了 5 秒延迟,适合测试慢速网站爬取或做爬取速度测试,减少本身网速干扰。
  • SPA 网站

  • spa1 :猫眼电影数据网站,数据通过 Ajax 加载,页面动态渲染,适合 Ajax 分析和动态页面渲染爬取。
  • spa2 :猫眼电影数据网站,数据通过 Ajax 加载,数据接口参数加密且有时间限制,适合动态页面渲染爬取或 JavaScript 逆向分析。
  • spa3 :猫眼电影数据网站,数据通过 Ajax 加载,无页码翻页,适合 Ajax 分析和动态页面渲染抓取。
  • spa4 :新闻网站索引,数据通过 Ajax 加载,无页码翻页,适合 Ajax 分析和动态页面渲染抓取以及智能页面提取分析。
  • spa5 :豆瓣图书网站,数据通过 Ajax 加载,有翻页,无反爬,适合大批量动态页面渲染抓取。
  • spa6 :电影数据网站,数据通过 Ajax 加载,数据接口参数加密且有时间限制,源码经过混淆,适合 JavaScript 逆向分析。
  • 验证码网站

  • captcha1 :对接滑动拼图验证码,适合滑动拼图验证码分析处理。
  • captcha2 :对接图标点选验证码,适合图标点选验证码分析处理。
  • captcha3 :对接图文点选验证码,适合图文点选验证码分析处理。
  • captcha4 :对接语序分析验证码,适合语序分析验证码分析处理。
  • captcha5 :对接空间推理验证码,适合空间推理验证码分析处理。
  • captcha6 :对接九宫格识图验证码,适合九宫格识图验证码分析处理。
  • 模拟登录网站

  • login1 :登录时用户名和密码经过加密处理,适合 JavaScript 逆向分析。
  • login2 :对接 Session + Cookies 模拟登录,适合用作 Session + Cookies 模拟登录练习。
  • login3 :对接 JWT 模拟登录方式,适合用作 JWT 模拟登录练习。
  • 反爬型网站

  • antispider1 :WebDriver 反爬网站,检测到 WebDriver 就不显示页面。
  • antispider2 :对接 User-Agent 反爬,检测到常见爬虫 User-Agent 就会拒绝响应,适合用作 User-Agent 反爬练习。
  • antispider3 :对接文字偏移反爬,所见顺序并不一定和源码顺序一致,适合用作文字偏移反爬练习。
  • antispider4 :对接字体文件反爬,显示的内容并不在 HTML 内,而是隐藏在字体文件,设置了文字映射表,适合用作字体反爬练习。
  • antispider5 :限制 IP 访问频率为最多 1 秒一个,如果过多则会封禁 IP。
  • app1 :最基本的 App 案例,数据通过接口加载,无反爬,无任何加密参数,适合做抓包分析和请求模拟。
  • app2 :设置了接口请求不走系统代理,因此无法直接抓包,适合做抓包特殊处理。
  • app3 :对系统代理进行了检测,如果设置了代理则无法正常请求数据,适合做抓包特殊处理。
  • app4 :设置了 SSL Pining,如果设置了非法证书则无法正常请求数据,适合做反 SSL Pining 处理。
  • app5 :接口增加了加密参数,适合做抓包实时处理或可视化爬取或逆向分析。
  • app6 :接口增加了加密参数,同时对源码进行了混淆,适合做抓包实时处理或可视化爬取或逆向分析。
  • app7 :接口增加了加密参数,同时对安装包进行了加固处理,适合做抓包实时处理或可视化爬取或逆向分析。
  • 暂且是这么多,后续还会继续增加,大家可以试着爬爬看。

    为了方便,我专门申请了一个域名,scrape.center,意思名为「爬取中心」,似乎听起来意义上还说的过去? 案例平台首页 URL: https://scrape.center ,截图如下: 案例首页 大家可以点击任意一个网站来爬取练习。

    下面是一些部分案例的截图: SSR1案例截图 SPA4案例截图 SPA5案例截图 SPA6案例截图 上面是一些案例的效果,基本上是使用 Django + Vue.js 开发的,主题使用了红色调,整个案例平台风格比较统一。另外还有一些 App 也是类似的风格,大家可以自行下载体验试试。 当然这里面最主要的还是案例的功能,比如各种加密、反爬、检测等等。

    有朋友可能会问这个案例平台的源代码在哪里。 这里解释一下,由于这个案例平台以后会用于案例的讲解,并且可能会出现在课程、书本中,所以为了避免盗版和抄袭的问题,这里我选择了闭源,也算是对自己的知识成果的另一种保护吧。 不过这并不意味着爬取代码是闭源的,这块还是会开源出来的。

    还有朋友会问,这一个个网站这么多类型和反爬,到底怎么爬呢? 其实现在针对这个练习平台一些案例讲解我已经做好了,课程也基本 OK 了,如果感兴趣大家可以点击看看。 https://t.lagou.com/cRC3RGRjSu706 谢谢大家。

    将时间段间隔内的内存数据以快照的形式写入磁盘,它恢复时是将快照文件直接读到内存里(snapshot)

    Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化号的文件。 主进程是不进行任何的IO操作,确保了极高的性能 。如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能会 丢失

    Fork:

    作用:创建一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)都与原进程一致。但为一个全新的进程,并作为原进程的子进程

    RDB保存的是dump.rdb文件

    配置位置:

    在redis.conf 中的snapshoting

    save,save只管保存,其他不管全部阻塞 bgsave:redis会在后台异步进行快照操作 快照同时还可以响应客户端请求,可以通过lastsave命令获取最后一次成功执行快照的时间 执行flush命令也会产生dump.rdb文件,但里面是空的,无意义

    如何恢复:

    将备份文件(dump.rdb)移动到redis安装目录,并启动服务即可 在连接完成之后的终端 使用 config get dir 获取目录 异常恢复: redis-check-rdb —fix {}

    适合大规模的数据备份 对数据完整性和一致性要求不高

    在一定时间间隔内做一次备份,所以如果redis意外down了就会丢失最后一次快照后的所有更改 Fork的时候,内存中的数据被克隆了一份,内存等将会2倍膨胀性,需考虑!!!

    如何停止:

    ​ 动态所有停止rdb保存规则方法:redis-cli config set save “”

    AOF:(Append only File):

    为什么还会出现AOF?(新技术的出现必定弥补老技术的不足,新技术一定会借鉴老技术,是老技术的子类) 如果一个系统里面同时存在RDB是冲突呢还是协作? 为什么AOF会在RDB之后产生 AOF它会有什么优缺点?

    原理: 以日志的形式来记录每个写操作 ,将Redis执行过的所有写 指令记录下来 (读操作不记录),只需追加文件但不可改写文件,Redis启动之初会读取该文件重新构建数据, 换言之,redis重启就根据日志文件的内容将写指令从前到后执行一次以完成数据恢复工作 配置位置: redis.conf中的APPEND ONLY MODE 配置说明: Appendfsunc:

    always: 同步持久化,每次发生更改立即记录到磁盘,性能差但数据完整性比较好 everysec:出厂默认推荐,异步操作,每秒记录,如果一秒内宕机,有数据丢失 No

    No-appendfsy-on-rewrite:重写时是否可以运用Appendfsync,默认no即可,保障数据安全 Auto-aof-rewrite-min-size :设定重写基准值 Auto-aof-rewrite-percentage : 设定重写基准值

    探讨dump.rdb,与aof的二者是否能共存及选择顺序

    当aof损坏时,rdb完全,二者可以和平共存 二者先选择aof

    AOF启动/修复/恢复:

    正常恢复:

    启动:将redis.conf中APPEND ONLY MODE 下appendonly no改为yes 将有数据的aof文件复制一份保存到对于目录(config get dir) 恢复: 重启redis然后重新加载即可

    异常恢复:

    备份被写坏的AOF文件, 修复: Redistribution-check-aof —fix 进行修复 重启redis,重新加载即可

    Rewrite:重写

    AOF采用文件追加的方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

    重新原理:

    AOF文件持续增长而过大时,会fork出一条新进程来讲文件重写(也是先写零食文件最后在rename),遍历新进程的内存数据,每条记录有一条的set语句.重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件.和快照有点类似

    触发机制: REDIS会记录上次重写时的AOF大小,默认配置时当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发 优势:

    每秒同步:appendfsync always 同步持久化 每次发送数据变更会被立即记录到磁盘,性能较差完整性比较友好 每修改同步: appendfsync everysec 异步操作,每秒记录,如果一秒内宕机,有数据丢失 不同步:appendfsync no 从不同步

    劣势: 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb AOF运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同

    Which one?:

    RDB持久化方式:能够在指定的时间间隔内对数据进行快照存储 AOF持久化方式:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重新,使得AOF文件的体积不至于过大 只做缓存:

    如果希望数据在服务器运行的时候存在,也可以不使用热河持久化方式.

    同时开启两种持久化方式:

    当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比rdb文件保存的数据集更完整 RDB的数据不实时,同时使用两者时服务器重启也指挥找AOF文件 那干脆直接使用AOF?不建议 因为RDB更适合用于数据库(AOF在不断变化不好备份),快速重启,而且不担保有AOF可能潜在的BUG,留着作为一个万一的手段

    浅谈排序算法与优化(仅部分,Updating)

    欢迎查阅与star的源码 写在最前面,此文章少了各排序算法的对比,但多了一份由浅入深的个人理解,以及代码、及算法的优化的思路 阅读文章约 需 5min

    将一组“无序”的记录序列调整为“有序”的记录序列

    将无序的列表变为有序列表 输入:列表; 输出:有序列表

    升序与降序 内置排序函数:sort(),基于timsort排序算法

    Timsort是一种混合稳定排序算法,源自归并排序(merge sort)和插入排序(insertion sort)

    有兴趣的伙计可以看看这两篇文章 sort算法运用原理1 sort算法运用原理2 TimSort

    常见排序算法

    列表每两个相邻的数,如果前面比后面大,则交换这两个数 一趟排序完成后,则无序区减少一个数,有序区增加一个数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @cal_time
    def bubble_sort(li: List[List]) -> List:
    print('初始列表', li)
    print('初始列表长度:', len(li))
    for i in range(len(li) - 1): # 第i躺,总次数
    i += 1
    for j in range(len(li) - i - 1): # 指针
    if li[j] > li[j + 1]: # 如果指针所对应的值大于对比的值,则二者交换位置
    li[j], li[j + 1] = li[j + 1], li[j]
    else:
    break
    print(f'冒泡排序第{i}次', li)

    li = [np.random.randint(0, 1000) for i in range(5)]
    bubble_sort(li)

    数据来源于numpy.randmo.randint()随机取数,运行流程:
    冒泡排序第1次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第2次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第3次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第4次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第5次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第6次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第7次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第8次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]
    冒泡排序第9次 [257, 620, 136, 379, 392, 118, 312, 892, 647, 655]

    # 排序次数:列表长度-1,
    # 缘由将无序区转化为有序区:(再看一遍以下这段代码, 如果li[j]>li[j+1],则二者交换位置)
    if li[j] > li[j + 1]:
    li[j], li[j + 1] = li[j + 1], li[j]

    # 当一维数组长度为10000的时候所需时间为:

    Runtime 优化: 在一次回头看看冒泡排序具体实现思路,将 列表每两个相邻的数,如果前面比后面大,则交换这两个数 , 一趟排序完成后,则无序区减少一个数,有序区增加一个数 。 如果无序区已经是有序的呢?按照代码执行流程可知, 如果前者比后者大,那么则两个交换位置。(假设为降序,前面为无序区,后面为有序区) 否则不执行任何交换操作,但 会执行便利 (也可以理解为此运算”不执行任何交换操作,无实际意义”) 优化目标:(将“不执行任何交换的操作去掉”),咱们在回头看看,具体实际的运算是在第二层for循环里面的,也就说所谓的“无用功”也是在这里产生的 无用功体现为:无论是否进行了位置交换,都会在往有序区在遍历检查一遍 思路如下: 主要优化的地方在跳出循环,在它不交换位置的时候,直接跳出此次的循环 假设它全部都是有序的,并设个标记True, 当如果循环内发生了位置交换,则改变标记为False。 流程控制,if。当if True的时则会执行if内部代码(设立return,或者break)主要跳出循环。避免对有序区进行有一次的排序操作 具体代码实现如下:

    def bubble_sort(li: List):
    print("The len of List:", len(li)) # 输入数组长度
    for i in range(len(li) - 1): # 保证输入的数组在计算范围类,此处的“-1”是因为索引=长度-1
    flag = True
    for j in range(len(li) - i - 1):
    if li[j] > li[j + 1]:
    li[j + 1], li[j] = li[j], li[j + 1]
    flag = False
    print("Sorting times:%d, Status:%s" % (i + 1, li))
    if flag:
    break

    视频都有一个唯一区分视频:BV 号 那么视频的 URL 规则为:’ https://wwww.bilibili.com/video/BV{BVID }’ 找一下弹幕的地址,直接 search,即可!如下

    由以上抓包可知,弹幕的 URL:’ https://api.bilibili.com/x/v1/dm/list.so?oid=oid ‘, 我们获取到 oid 那么这一步就完成了 来,回头去找一下 oid 从何而来呢? 据老夫多年经验指引,他一定在视频 URL 里面。(其实当时也找了挺久的,甚至逆向那一手,断点调试、调用堆栈等等什么都用出来了。最终还是功夫不负有心人,找到了) 其实回头看,oid 是等于 video_URL 页面里面的 cid 参数的(验证了 Payne 式猜想)。过程是难受的

    URL,其参数规则也找到了,那么还不就随我为所欲为了。只要拿到视频地址,那不就可以直接拿到弹幕了么。of course!

    此处省略 3 万字(请求,解析,网络原理。。。)

    其实当时知道两个方法都去试了,JS 那个就不说了,有兴趣的盆友,可以去搞一下 说说这个提取 cid 参数吧,我用的是正则,这种情况最好是用正则,不过也看个人喜好吧。 可以回头看第二张图,初一乍看我好像不会,啊哈哈~

    经过优化后(主要是看了其他视频的那啥之后):写出这个神奇的正则

    补充知识:

    迭代、递归与循环:迭代与递归都是循环的子集,一个是取值推算,一个是不断的调用自己。 相同点:迭代、递归、循环都是“重复” 相似点:调用逻辑相似 不同点:我简单理解为迭代是根据自身的上一个值推算下一个值,而递归则是由上一个值与“己身”直接运算。循环是自身与外界计算 堆栈关系调用不同 当然也不能说谁好谁坏,只能说三者主针对不同

    来,来,来,翠花上栗子:

    递归,归去来兮 :

    算法是对特定问题求解 步骤 的一种描述,如果将问题看作函数,那么算法是吧输入转化为输出

    是对特定问题求解步骤的一种描述,是为了解决一个或者一类问题给出的 一个确定的、有限长的操作序列。 算法的设计依赖于数据的存储结构,因此对确定的问题,应该需求子啊适宜的存储结构上设计出一种效率较高的算法

    算法的重要特性:

    对于任何一组合法的输入值,在执行有穷步骤之后一定能结束,即算法中的操作步骤为有限个,并且每个步骤都能在有限的时间内完成

    对于每种情况下所应该执行的路径的操作,在算法中都有确切的规定,使算法的执行者或阅读者都能明确其含义及如何执行;并且在确切的条件下只有唯一一条执行流程路径

    算法中的所有操作都必须足够基本,都可以通过已经实现的基本运算执行有限次实现

    作为算法加工对象的量值,通常体现为算法中的一组变量。有些输入量需要在算法的执行过程中输入,而有些算法表面上没有输入,但实际上已被嵌入算法之中

    它是一组与“输入”有确定关系的量值,是算法进行信息加工够得到的结果。这种确定关系即为算法的功能

    算法设计目标:

    算法应满足具体问题的需求,正确反映求解问题对输入、输出加工处理等方面的需求

  • 程序中不含语法错误,计算的结果却不能满足规格说明要求。
  • 程序对于特定的几组输入数据能够得出满足要求的结果,而对于其他的输入数据的不出正确的计算结果;
  • 程序对于精心选择的、经典、苛刻且带有刁难性的几组输入数据能够得到满足需求的结果;(正确的算法)
  • 程序对于一切合法的输入数据都能得出满足要求的结果
  • 算法处理用于编写程序子啊计算机上执行之外,另一个重要用处是阅读和交流。 算法中加入适当的注释,介绍算法的设计思路、各个模块的功能等一些必要性的说明文字来帮助读者理解算法。 要求: 算法中加入适当的注释,介绍算法的设计思路、各个模块的功能等一些必要性的说明文字来帮助读者理解算法 对算法中出现的各种自定义变量和类型能做到“见名知义”,即读者一看到某个变量(或类型名)就能知道其功能

    当输入数据非法时,算法能够适当地做出反应或进行处理,输出表示错误性质的信息并终止执行,而不会产生莫名其妙的输出结果。

    时间效率与存储占用量:

    一般来说,求解同一个问题若有多种算法,则 执行时间短的算法效率高 占用存储空间少的算法较好 算法的执行时间开销和存储空间开销往往相互制约,对高时间效率和低存储占用的要求只能根据问题的性质折中处理

    算法复杂度

    算法的时间复杂度:

    算法的效率指的是算法的执行时间随问题“规模”(通常用整型量 n 表示)的增长而增长的趋势 “规模”在此指的是输入量的数目,比如在排序问题中,问题的规模可以是被排序的元素数目 假如随着问题规模 n 的增长,算法执行时间的增长率和问题规模的增长率相同则可记为: T(n) = O(f(n))

    f(n) 为问题规模 n 的某个函数; T(n)被称为算法的(渐进)时间复杂度(Time Complexity) O 表示法不需要给出运行时间的精确值; 选择 f(n),通常选择比较简单的函数形式,并忽略低次项和系数 常用的有 O(1)、O(logn)、O(n)、O(nlogn)、O(n*n)等等 多项式时间复杂度的关系为: O(1) < O(logn) < O(n) < O(N logn) < O(n²) < O(n³) 指数时间算法的关系为: O(2(n 方))< O(n!) <O(n(n 方))

    由于估算算法时间复杂度关心的只是算法执行时间的增长率而不是绝对时间,因此可以忽略一些因素。 方法:从算法中选取一种对于所研究的问题来说是“ 基本操作” 的原操作,以该“基本操作”在算法中重复执行的次数作为算法时间复杂度的依据。 EG:

    两个 n x n 的矩阵相乘,求其时间复杂度

    为了降低复杂度,一个直观的思路是:梳理程序,看其流程中是否有无效的计算或者无效的存储。我们需要从时间复杂度和空间复杂度两个维度来考虑。 常用的降低时间复杂度的方法有递归、二分法、排序算法、动态规划等, 降低空间复杂度的方法,就要围绕数据结构做文章了。 降低空间复杂度的核心思路就是,能用低复杂度的数据结构能解决问题,就千万不要用高复杂度的数据结构。

    如何评定一个程序算法的好坏?

    简而言之:在符合算法设计标准的前提下,运行的更快、所用计算资源更少。即是更好的算法

    请注意这里的比较级别词 !!!,对于算法,个人认为就是获得同一结果的同时探究 最优 解决之道

    探究算法优化(自我一点点小体悟-个人能力有限)

    抽象化:将 不必要 的计算过程去掉

    利用高斯算法解决累计求和问题

    慎选各 数据结构 ,善用 第三方 算法

    去重: 善用迭代什么的等等 使用字典的特性-不可重复性 布隆过滤器去重(源于哈希算法)

    时空转换:将 昂贵的 时间转化为 廉价的 空间

    当‘优无可优时’,取贵舍廉 空间是可以用 买的,加个什么 内存 啊,加个 处理器 等等什么的 时间是逝去就不在了

    以上仅仅个人的一点点小灵感,望大家不喜勿喷

    说了这么多,来道简单的算法题目导入(同一计算机,i5, python) 需求如下:累计求和 1-n 的值(1. 为防止误差,验证 10 次; 2. 验证每次计算次数)

    # 在同一空间复杂下(也就是说没有迭代,探究暴力解法与高斯算法)
    # n = 100000
    # Method one 代码如下
    def func(n):
    start = time.time()
    count = 0
    theSum = 0
    for i in range(n + 1):
    theSum = theSum + n
    count += 1
    end = time.time()
    return theSum, end - start, count

    for i in range(10):
    print(f"Sum is %d Required %10.10f seconds count = %d" % func(10000))
    # %10.10f 表示取10位小数,运行结果如下

    Sum is 100010000 Required 0.0010061264 seconds count = 10001 Sum is 100010000 Required 0.0009891987 seconds count = 10001 Sum is 100010000 Required 0.0000000000 seconds count = 10001 Sum is 100010000 Required 0.0010235310 seconds count = 10001 Sum is 100010000 Required 0.0009710789 seconds count = 10001 Sum is 100010000 Required 0.0000000000 seconds count = 10001 Sum is 100010000 Required 0.0009973049 seconds count = 10001 Sum is 100010000 Required 0.0010013580 seconds count = 10001 Sum is 100010000 Required 0.0000000000 seconds count = 10001 Sum is 100010000 Required 0.0019786358 seconds count = 10001 探究可知:n 扩大 10 倍,运算时间也会扩大 10 倍 高斯算法,从 1 累加至 n,等于(首项+尾项) 项数/2 直接引用结论: 1+2+3+…+(n-1) +n={(1+n)+(2+(n-1))…}/2 = (n (n + 1))/2

    计算结果如下: 循环计算 10000 次,出去 I/O 所需时间几乎可以不计。 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000123123000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.000000000000000000000000000000000000000000000000012332100000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.00000000000000000000000000000000000000000000000000000002310000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.000000000000000000000000000000000000000000000000000000010032000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000012231300000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552

    class Singleton(object):
    # def __new__(slef):类方法
    # pass
    # 当我们没写时默认调用object__new__方法

    # 然后在执行类的实例化对象:__init__
    def __init__(self): # 实例方法
    pass

    @classmethod
    def instance(cls, *args,**kwargs):
    if not has attr(Singleton,"_instance"):
    Singleton._instance = Singleton(*args,**kwargs)
    return Singleton._intance
    class Singleton(object):

    def __init__(self):
    pass

    @classmethod
    def instance(cls, *args, **kwargs):
    if not hasattr(Singleton, "_instance"):
    Singleton._instance = Singleton(*args, **kwargs)
    return Singleton._instance

    import threading

    def task(arg):
    obj = Singleton.instance()
    print(obj)

    for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

    当然此时也并没有什么问题,BUT在’ init ‘方法中加入I/O(input/output)操作就凉凉了 问题出现了,按照以上方式创建的单例无法支持 多线程 缘由:Python中实例化对象与初始化对象是分开执行的,又由于多线程之间是通信共享的,故出现线程安全问题。主要体现为,create一个之后kill一个,create一个又被kill一个。所以就。。。 解决思路一:相互独立,分而治之。加锁独立 也就是咱们所了解、知道的线程锁的概念,使得其无序变为相对有序。具体代码便不在此赘述 在看看思路一(相互独立,分而治之。加锁独立) 解决思路二:‘反’实例化,加锁保护独立,确保通用性 在Python3中,调用父类方法是为super(),那么是否可以增加判断: 当类属性不为空时,我们便不在实例化且返回一个 已实例化 的类属性。这样还是不太完美,带有局限性。进一步加锁保护优化以保障多线程情况下只有一个线程同时访问。这样就保障了单例的安全 基于 new 方法实现!!!

    // from * import singleton 需使用时,直接在其他文件中导入此文件中的对象,那么这个对象即是单例模式对象 还有个基于元类的就没书写了具体请看: https://blog.csdn.net/weixin_44239343/article/details/89376796

    面试题之二:Redis有几种数据类型?

    如果是单单是Redis那么 常用数据类型为五种 他们分别是:String,List,hash,set,zset String:字符串,一个字符串Value最多可以是512M Hash:哈希,是一个String类型的field和Value的映射表 List:列表,时间是链表 Set:集合是一个String类型无序无重复集合,其通过Hash Table实现 Zset(sorted):有序集合 那么应聘时,请注意这个小坑,你 Python 使用Redis又几种数据类型? 这个是基于语言来回答的,所用语言+Redis数据类型杂糅 Number,String,list,tuple(这个不确定),dict,aggregate 同时又涉语言所拥有的数据类型与redis,一样的就 ‘合二为一’ 嘛 以Python为例,稍后继续探究这(6+5)之间的杂糅,dict与aggregate其二者区别为主(其实我也不晓得更深的了)。以及1对1,1对多,多对1。数据结构搞起来,然后哼哼~。

    面试题之三:Scrapy框架的运行流程及各模块的作用

    如果简历里面写了分布式会拓展scrapy-Redis架构以及其作用。 CAP理论,估计会扯到数据这块。拓展database什么特性啊,之类的。谈优化,谈数据结构。反正数据结构与算法这块,基于此,难于此,也凉于此

    面试题之四:scrapy去重所用的几种机制

    谨记:先从scrapy本身的去重原理及机制说起来,最基础,优缺点,去重原理等等。一步步来,一上来就BloonFilter,风险不小啊 对于此,自我总结如下:

    1、scrapy 基于内存

    之前我写过几篇文章介绍过有关爬虫的智能解析算法,包括商业化应用 Diffbot、Readability、Newspaper 这些库,另外我有一位朋友之前还专门针对新闻正文的提取算法 GeneralNewsExtractor,这段时间我也参考和研究了一下这些库的算法,同时参考一些论文,也写了一个智能解析库,在这里就做一个非正式的介绍。

    那首先说说我想做的是什么。 比如这里有一个网站,网易新闻, https://news.163.com/rank/,这里有个新闻列表,预览图如下 image-20200705212920277 任意点开一篇新闻,看到的结果如下: image-20200705213058853 我现在需要做到的是在不编写任何 XPath、Selector 的情况下实现下面信息的提取: 对于列表页来说,我要提取新闻的所有标题列表和对应的链接,它们就是图中的红色区域: image-20200705213401471 这里红色区域分了多个区块,比如这里一共就是 40 个链接,我都需要提取出来,包括标题的名称,标题的 URL。 我们看到页面里面还有很多无用的链接,如上图绿色区域,包括分类、内部导航等,这些需要排除掉。 对于详情页,我主要关心的内容有标题、发布时间、正文内容,它们就是图中红色区域: image-20200705213723198 其中这里也带有一些干扰项,比如绿色区域的侧边栏的内容,无用的分享链接等。 总之,我想实现某种算法,实现如上两大部分的智能化提取。

    之前我开发了一个叫做 Gerapy https://github.com/Gerapy/Gerapy 的框架,是一个基于 Scrapy、Scrapyd 的分布式爬虫管理框架,属 1.x 版本。现在正在开发 Gerapy 2.x 版本,其定位转向了 Scrapy 的可视化配置和调试、智能化解析方向,放弃支持 Scraypd,转而支持 Docker、Kubernetes 的部署和监控。 对于智能解析来说,就像刚才说的,我期望的就是上述的功能,在不编写任何 XPath 和 Selector 的情况下实现页面关键内容的提取。 框架现在发布了第一个初步版本,名称叫做 Gerapy Auto Extractor,名字 Gerapy 相关,也会作为 Gerapy 的其中一个模块。 GitHub 链接: https://github.com/Gerapy/GerapyAutoExtractor 现在已经发布了 PyPi, https://pypi.org/project/gerapy-auto-extractor/,可以使用 pip3 来安装,安装方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    [
    {
    "title": "1家6口5年"结离婚"10次:儿媳""公公岳",
    "url": "https://news.163.com/20/0705/05/FGOFE1HJ0001875P.html"
    },
    {
    "title": ""港独"议员泼水阻碍教科书议题林郑月娥深夜斥责",
    "url": "https://news.163.com/20/0705/02/FGO66FU90001899O.html"
    },
    {
    "title": "感动中国致敬留德女学生:街头怒怼"港独"有理有",
    "url": "https://news.163.com/20/0705/08/FGOPG3AM0001899O.html"
    },
    {
    "title": "香港名医讽刺港警流血少过月经受访时辩称遭盗号",
    "url": "https://news.163.com/20/0705/01/FGO42EK90001875O.html"
    },
    {
    "title": "李晨独居北京复式豪宅没想到肌肉男喜欢小花椅子",
    "url": "https://home.163.com/20/0705/07/FGOLER1200108GL2.html"
    },
    {
    "title": "不战东京!林丹官宣退役正式结束20年职业生涯",
    "url": "https://sports.163.com/20/0704/12/FGML920300058782.html"
    },
    {
    "title": "香港美女搬运工月薪1.6万每月花6千租5平出租",
    "url": "https://home.163.com/20/0705/07/FGOLEL1100108GL2.html"
    },
    {
    "title": "杭州第一大P2P"凉了":近百亿未还!被警方立案",
    "url": "https://money.163.com/20/0705/07/FGON5T7B00259DLP.html"
    },
    ...
    ]
    {
    "title": "内蒙古巴彦淖尔发布鼠疫疫情Ⅲ级预警",
    "datetime": "2020-07-05 18:54:15",
    "content": "2020年7月4日,乌拉特中旗人民医院报告了1例疑似腺鼠疫病例,根据《内蒙古自治区鼠疫疫情预警实施方案》(内鼠防应急发﹝2020﹞7号)和《自治区鼠疫控制应急预案(2020年版)》(内政办发﹝2020﹞17号)的要求,经研究决定,于7月5日发布鼠疫防控Ⅲ级预警信息如下:n一、预警级别及起始时间n预警级别:Ⅲ级。n2020年7月5日起进入预警期,预警时间从本预警通告发布之日持续到2020年底。n二、注意事项n当前我市存在人间鼠疫疫情传播的风险,请广大公众严格按照鼠疫防控“三不三报”的要求,切实做好个人防护,提高自我防护意识和能力。不私自捕猎疫源动物、不剥食疫源动物、不私自携带疫源动物及其产品出疫区;发现病(死)旱獭及其他动物要报告、发现疑似鼠疫病人要报告、发现不明原因的高热病人和急死病人要报告。要谨慎进入鼠疫疫源地,如有鼠疫疫源地的旅居史,出现发热等不适症状时及时赴定点医院就诊。n按照国家、自治区鼠疫控制应急预案的要求,市卫生健康委将根据鼠疫疫情预警的分级,及时发布和调整预警信息。n巴彦淖尔市卫生健康委员会n2020年7月5日n来源:巴彦淖尔市卫生健康委员会"
    }

    成功输出了标题、正文、发布时间等内容。 这里就演示了基本的列表页、详情页的提取操作。

    整个算法的实现比较杂,我看了几篇论文和几个项目的源码,然后经过一些修改实现的。 其中列表页解析的参考论文:

  • 面向不规则列表的网页数据抽取技术的研究
  • 基于块密度加权标签路径特征的 Web 新闻在线抽取
  • 详情页解析的参考论文和项目:

  • 基于文本及符号密度的网页正文提取方法
  • GeneralNewsExtractor
  • 这些都是不完全参考,然后加上自己的一些修改最终才形成了现在的结果。 算法在这里就几句话描述一下思路,暂时先不展开讲了。 列表页解析:

  • 找到具有公共父节点的连续相邻子节点,父节点作为候选节点。
  • 根据节点特征进行聚类融合,将符合条件的父节点融合在一起。
  • 根据节点的特征、文本密度、视觉信息(尚未实现)挑选最优父节点。
  • 从最优父节点内根据标题特征提取标题。
  • 详情页解析:

  • 标题根据 meta、title、h 节点综合提取
  • 时间根据 meta、正则信息综合提取
  • 正文根据文本密度、符号密度、视觉信息(尚未实现)综合提取。
  • 后面等完善了之后再详细介绍算法的具体实现,现在如感兴趣可以去看源码。

    本框架仅仅发布了最初测试版本,测试覆盖度比较少,目前仅仅测试了有限的几个网站,尚未大规模测试和添加对比实验,因此准确率现在还没有标准的保证。 参考:关于详情页正文的提取我主要参考了 GeneralNewsExtractor 这个项目,原项目据测试可以达到 90% 以上的准确率。 列表页我测试了腾讯、网易、知乎等都是可以顺利提取的,如: 19841593922229_.pic_hd image-20200705224404571 image-20200705224419759 后面会有大规模测试和修正。 项目初版,肯定存在很多不足,希望大家可以多发 Issue 和提 PR。 另外这里建立了一个 Gerapy 开发交流群,之前在 QQ 群的也欢迎加入,以后交流就在微信群了,大家在使用过程遇到关于 Gerapy、Gerapy Auto Extractor 的问题欢迎交流。 这里放一个临时二维码,后期可能会失效,失效后大家可以到公众号「进击的 Coder」获取加群方式。 image-20200705225922008

    待开发功能

  • 视觉信息的融合
  • 文本相似度的融合
  • 分类模型的融合
  • 下一页翻页的信息提取
  • 正文图片、视频的提取
  • 对接 Gerapy
  • 最后感谢大家的支持!

    主要亮点为配置和密码找回,安装什么的就。。。

    MySQL 基本配置

    官网地址:www.mysql.com 安装可参考: https://cuiqingcai.com/5200.html

    window:

    [gallery columns=”1” size=”full” ids=”9457”] 注意终端 mysqld 开启的不能关闭!

    MySQL 密码找回:

    密码验证思路:mysql 必定将管理员账号密码存储在某个文件夹内,使用时与输入密码验证,成功则能够连接,否则连接失败。 密码找回思路: 跳过 MySQL 密码的验证直接进入

    停止 mysql 服务(注:需要终端的管理员权限运行)

    // windows:

    查询进程,并找到 PID:

    tasklist | findsrt mysqld
    
    1
    2
    3
    4
    2.  kill 掉 mysql 进程,否则是停止不了服务的,无论如何都需要 kill 掉:

    ![](https://cdn.cuiqingcai.com/wp-content/uploads/2020/06/change_password1.png)

    #此处的为上面查询到的PID,每次都是不一样的,所以就不写具体值了 taskkill /F /PID PID

    git remote add [origin] [Warehouse URI] 创建别名为 origin,github 仓库地址为。。。的 git remote -v 查看相关信息

    推送操作:

    使用命令:

    git remote -v

    git fetch [远程库中简称(origin)] [远程库分支名(master)]

    git merge [远程库中简称/远程库分支名(origin/master)]

    fetch:

  • 首先是查看了 remote
  • 然后使用 git fetch 命令下载了远程库内容
  • 查看本地库中的 project.txt 内容
  • 切换到新下载的 master 目录(可使用 git reflog 查看分支状态)
  • 查看远程库中 project.txt 内容
  • merge:将远程库下载的内容与本地库合并

  • 缘由:如果不是基于远程库的最新版本进行修改的
  • 解决:不能推送,必须先拉,拉下来进入冲突状态,进行对内容的修改,删除不相关内容,重新处理即可。更详情请查看前面的 push 冲突解决。
  • 团队外协作:

    流程: 具体的流程可以参考上面的流程图:首先是 fork 一个仓库,然后拉去请求 后 clone 到本地,然后编辑更改。后推送到远程仓库(这里的克隆操作就省略了,具体的可以看上面) 发送即可完成。 然后接收者根据流程图步骤 6 开始操作即可

  • 首先使用 vim 命令新建了一个名为 hard.txt,并在里面写入项目数据‘aaa、bbb’
  • 使用 git add hard.txt 命令将 hard.txt 添加到暂存区
  • 使用 git commit hard.txt 命令将 hard.txt 添加到 本地库
  • 使用命令 git reflog 查看相关的将要操作的参考
  • 给‘项目’版本进行更新:

    更新,并将其 commit 到本地库中

    基本的已完成了,让我们来试试版本的前进与后退,并查看相对应的内容

    git reflog

    git reset —hard [索引值] //前进与后退索引命令

    注意观察 cat hard.txt 的内容!!!

    版本后退:

    基于符号的版本控制

    合并分支注意:首先对其他分支进行编辑更新添加新功能,然后需切换到主分支 master 上,执行 merge 命令,完成合并。便可更新增的功能

    // 解决冲突(手动),当不能直接使用 merge 合并时,则需要

    何时会出现合并错误?又如何去修改?

    在不同分支出现出现相对应的更改时,自动合并不知道以何更新为主,合并将产生冲突;

    重新 add,commit 注意 commit 不能带文件名

  • 编辑文件,删除特殊符号
  • 把文件修改至满意,保存并退出
  • git add 【file name】
  • git commit -m “log message”

  • 此处的 commit 不能带 file name
  • 简介:了解过 git 来源的朋友,应该会晓得 git 与 Linux 系统是同一作者,所以在此操作 Linux 的基础命令几乎都能运行,在此便不在过多赘述: 列举几个在此常用的命令:

  • ls -lA:查看所有的目录(包含隐藏文件夹)
  • ls -l|less 分屏的去查看
  • 安装好了之后如果是默认安装,git 会添加到鼠标右击的快捷栏中(如下所示):

    Git 使用前许初始化本地库,具体操作如下:

    本地库初始化:(命令行:Git Bash Here)

  • 命令:git add(使用 git bash here)
  • 进入相对应的目录下,新建一个用于学习 Git 的文件夹

  • 首先是使用了’ll‘,命令查看当前前目录先文件夹及权限
  • 使用 mkdir anothergit 命令在当前目录下创建了一个名为 anothergit 的文件夹
  • 使用 git init ,初始化本地库
  • 初始化成功效果展示:

    ll -lA // 显示该目录下全部的文件夹(注:如果使用’ll‘是不能显示出’.git/‘的)
                          
    ll .git/ // 查看.git/文件夹下的可显示的文件夹
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    注:.git 目录中存放的是本地库相关的配置文件,请勿随意删除及修改。(这里凉了就凉凉了,git 的命令也帮不了你)

    ## 设置签名:

    主要用于区分开发者身份,并非会验证验证其邮箱真实性,

    - ​ 形式:
    - 用户名:Payne
    - Email 地址:[email protected]
    - 这里设置的前和登录远程库的账号密码无任何关系
    - 签名:如果都有的话会以项目级别仓库签名为使用,否则会以系统用户级别为使用

    - 项目级别(仓库级别):仅在此库有效
    - 命令:
    //主要用的命令 git config // 实际用的 git config user.name Payne_project git config user.eamil [email protected]
    1
    2
    3

    - 实际操作:
    - ![](https://cdn.cuiqingcai.com/wp-content/uploads/2020/05/4.项目级别签名设置.png)使用命令,查看项目级别签名设置结果:
    cat ./git/config
    1
    2
    3

    - 系统用户级别:登录当前操作系统的用户范围(包含多个项目级别,仓库级别)
    - 命令:
    //主要用的命令 git config --global // 实际用的 git config --global user.name Payne_global git config --global user.eamil [email protected]
  • 实际操作系统用户级别的 config 是在系统目录下,如果不想太麻烦的去找直接 cat ~/.git config 即可
  • 这篇文章是我近日学习 git 的笔记,单纯想做个“云备份什么的”,所以的话侧重点就有点偏。

    Git 的来源、用处相信大家都或多或少的了解,如果真的想知道的话请自行百度哈~

    Git 的安装:

    推荐镜像安装:https://npm.taobao.org/mirrors/git-for-windows/ Git 官网地址

    https://git-scm.com/

    Git 安装相关简介>):

    https://git-scm.com/download

    各系统的安装(并非唯一方式!)

    Window Git 下载地址:

    https://git-scm.com/download/win

    选择相对应的版本下载即可,下载完成后打开相对应的安装执行,有选项的选择的建议点击第一个默认配置,就不在此过多赘述啦。

    MacOS:在命令行中输入以下命令即可

    brew install git

    以上的网页地址为: https://git-scm.com/download/mac

    Linux:(sudo:以管理员权限运行相关的命令,中间的‘-y’:默认同意安装)

    在这里以 Ubuntu 为例: Ubuntu:

    apt-get install git apt-get -y install git sudo apt-get install git sudo apt-get -y install git

    更加具体的可自行查阅 Linux 系统安装 Git 相关:

    https://git-scm.com/download/linux

    Git 和代码托管中心

    代码托管中心的任务:维护远程库

    局域网环境下:

  • 可搭建 GitLab 服务器
  • 外网环境下:

  • Github
  • 并发与并行:(偏向于多线/进程方面的原理)

  • 并发: 指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行
  • 并行: 指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的
  • 阻塞与非阻塞:(偏向于协程/异步的原理)

  • 阻塞:阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续处理其他的事情,则称该程序在该操作上是阻塞的。
  • 非阻塞:程序在等待某操作过程中,自身不被阻塞,可以继续处理其他的事情,则称该程序在该操作上是非阻塞的

    同步与异步:

  • 同步:不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,我们称这些程序单元是同步执行的。
  • 异步:为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。

    说了这么多,咱们列举一些他们相关的特点吧:

  • 多线程(英语:multithreading):指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”
  • 多进程(Multiprocessing):每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
  • 二者的区别:线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文.多线程主要是为了节约 CPU 时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和 CPU。
  • 协程(Coroutine):又称微线程、纤程,协程是一种用户态的轻量级线程。 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

    基本的原理都已经了解了 ,那咱们不整一下,咋行?光说不练假把式,走起!!! 本节源码:仓库地址>) 首先先说一下基本的思路:

  • 确定 URL
  • 发起请求,得到响应
  • 解析响应,提取数据、
  • 确定 URL:

    本次请求的 URL(先放地址了!) https://www.guazi.com/cs/buy/o2/#bread

    async def parse(self, html):
    with open('car.csv', 'a+', encoding='utf-8') as f:
    doc = pq(html)
    for message in doc('body > div.list-wrap.js-post > ul > li > a').items():
    # 汽车简介
    car_name = message('h2.t').text()
    # 汽车详情(年限、里程、服务)
    car_info = message('div.t-i').text()
    year = car_info[:5]
    mileage = car_info[6:-5]
    service = car_info[13:].replace('|', '')
    # 价格
    try:
    price = message('div.t-price > p').text()
    except AttributeError:
    price = message('em.line-through').text()
    car_pic = message('img').attr('src')
    data = f'{car_name}, {year},{mileage}, {service}, {price}n'
    logging.info(data)
    f.write(data)
  • 加密:数据加密的基本过程,将原为明文的文件或数据经过某种算法进行一次或多次处理。得到的结果常称之为密文的东东。
  • 解密:加密的逆过程,找到加密相同的方式,对其逆向处理,得到原本文件或数据的过程
  • 常用的加密方式:

    加密算法分 对称加密非对称加密 其中对称加密算法的加密与解密 密钥相同,非对称加密算法的加密密钥与解密 密钥不同,此外,还有一类 不需要密钥散列算法

    本节所涉及的方式:MD5

    MD5 用的是 哈希函数,它的典型应用是对一段信息产生 信息摘要,以 防止被篡改。严格来说,MD5 不是一种 加密算法 而是 摘要算法。无论是多长的输入,MD5 都会输出长度为 128bits 的一个串 (通常用 16 进制 表示为 32 个字符)。 更多相关详情请点击此处>)

    二 造!点击进入本节源码

    这段内容图会比较多,文字叙述会比较少.

    确定 URL:

    Basic URL : http://fanyi.youdao.com/ 结论缘由,在不刷新全局页面的情况下,在输入框中输入,翻译动态刷新.可知此链接为 Ajax. 经过一系列测试发现,其实际需操作的 URL 为 http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule 在开发者工具中具体观察以下. 基本网站的分析就分析完毕了 注意此处为 POST 请求!!!

    仔细观察红色方框中,重点观察随着时间改变而改变的参数(图中红色箭头所指之处)

    分析加密:

    仔细经过上述步骤即可进入本次加密的源码详情页 搜索 sign 参数,得知本页面有 15 个 sign,筛选排查过后可得知以下位置为 sign 等参数,赋值加密过程 为什么会大概确定是此处呢? 理由一:var 声明赋值 理由二:md5() 为什么深信此处呢?

    断点一打,debug 一下,啥都出来了.

    根据其语法可知,Javascript

  • e 为输入所翻译的内容
  • ts 为七位整数的时间戳
  • salt 为时间戳后加上一位,大于 0 小于 9 的数字
  • bv 为 User-Agent 的值经过md5 加密的 密文
  • sign 为(“fanyideskweb” + e + salt + “Nw(nmmbP%A-r6U3EUn]Aj”)经过 md5 加密的 密文
  • 到这里就基本完成了,那接下来就开始码码吧.

    看到这里,转而看一下源码。对着上面的注释,仔细看看,相信你一定会有所收获的。

    可以将此源码打包,并建立用户界面

    其实这篇完全就是用来找感觉的,真正的 JS 难度系数成几何倍增长,所以...

    加油吧,欧里给~ 如有疑问,那么评论区见

    什么是虚拟环境?

  • 百度百科>)得知: 以专利的实时动态程序行为修饰与模拟算法,直接利用本机的 OS,模拟出自带与本机相容 OS 的虚拟机(Vista 下可模拟 Vista、XP,Windows 7 下则可模拟 Windows 7、Vista、XP),也称为“虚拟环境”
  • 功能: 每一个环境都相当于一个新的 Python 环境。你可以在这个新的环境里安装库,运行代码等
  • 为什么需要使用虚拟环境?

  • 众所周知 Python 的强大在于其兼容性,其强大的社区等。同时缺也由些许库并不兼容
  • 真实环境与虚拟环境二者相对关联,并非绝对关联,可以在环境里面随便造。
  • 什么时候需要使用虚拟环境?

  • 需要探究不同版本的 Django 等相互之间的异同
  • 各模块产生冲突时
  •