目录

前言 1.1

爬虫简介 1.2

爬虫的叫法 1.2.1

为何叫爬虫 1.2.2

爬虫应用领域 1.2.3

爬虫的核心逻辑 1.3

爬虫的核心流程 1.3.1

爬虫的核心步骤 1.3.2

爬虫的典型实现方式 1.3.3

抓包分析 1.4

抓包分析网页 1.4.1

静态网页 1.4.1.1

动态网页 1.4.1.2

抓包分析app 1.4.2

爬虫框架 1.5

为何需要爬虫框架 1.5.1

常见爬虫框架 1.5.2

如何写爬虫 1.6

用Python写爬虫 1.6.1

用C#写爬虫 1.6.2

C#模拟登录百度首页 1.6.2.1

用Go写爬虫 1.6.3

Go模拟登录百度首页 1.6.3.1

用Java写爬虫 1.6.4

Java模拟登录百度首页 1.6.4.1

用PHP写爬虫 1.6.5

附录 1.7

名词解释 1.7.1

参考资料 1.7.2

前言

爬取你要的数据：爬虫技术

最新版本：v2.9

更新时间：20200918

简介

整理爬虫技术的各种叫法，解释为何叫做爬虫，为何又被叫做模拟登陆，与爬虫有关的一些东西，总结爬虫的核心步骤和阶段，以及每一步的各种细节包括优缺点和其他涉及的内容，继续解释为何要用爬虫框架，总结常见语言的各种爬虫框架，总结实现爬虫的不同方式和爬虫的不同步骤之间的对应关系，用不同语言如何写爬虫，并且给出常见语言如Python、C#、Go、Java、PHP等的具体爬虫实例代码，最后总结爬虫相关名词和概念。

源码+浏览+下载

本书的各种源码、在线浏览地址、多种格式文件下载如下：

Gitbook源码

crifan/crawl_your_data_spider_technology: 爬取你要的数据：爬虫技术

如何使用此Gitbook源码去生成发布为电子书

详见：crifan/gitbook_template: demo how to use crifan gitbook template and demo

在线浏览

爬取你要的数据：爬虫技术 book.crifan.org

爬取你要的数据：爬虫技术 crifan.github.io

离线下载阅读

爬取你要的数据：爬虫技术 PDF

爬取你要的数据：爬虫技术 ePub

爬取你要的数据：爬虫技术 Mobi

版权说明

此电子书教程的全部内容，如无特别说明，均为本人原创和整理。其中部分内容参考自网络，均已备注了出处。如有发现侵犯您版权，请通过邮箱联系我 admin 艾特 crifan.org，我会尽快删除。谢谢合作。

鸣谢

感谢我的老婆陈雪的包容理解和悉心照料，才使得我crifan有更多精力去专注技术专研和整理归纳出这些电子书和技术教程，特此鸣谢。

更多其他电子书

本人crifan还写了其他100+本电子书教程，感兴趣可移步至：

crifan/crifan_ebook_readme: Crifan的电子书的使用说明

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2021-09-18 14:11:16

爬虫简介

爬虫简介

爬虫，此处主要指的是，能够从网站的页面或app等数据源中爬取到你所需要的数据的代码程序。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:20

爬虫的叫法

爬虫的叫法

爬虫有很多种常见的叫法，整理如下：

爬虫

常见英文说法：

crawler=爬取数据的工具

crawl英文原意：爬，爬行

spider=蜘蛛=像蜘蛛捕获昆虫一样你去捕获你要的数据

spider英文原意：蜘蛛

为何把爬取数据的工具叫做蜘蛛，见后续的类比解释：为何叫爬虫

scraper=刮取到你想要的数据的工具

scrape英文原意：刮取

grab=抓取你要的数据的工具

grab英文原意：攫取，夺取

爬取数据

常见英文说法：

crawl data=crawling data

scraping data

grabbing data

爬取网站=爬取网页

常见英文说法：

crawl website

模拟登录

常见英文说法：

emulate login

login emulation

为何（把爬虫）叫做模拟登录？

简答：

爬虫在需要用户登录的场景中应用时的叫法

详解：

对于一些情况：想要爬取很多网站上的数据，需要用户（使用账号和密码等方式）去登录后才能获取到

所以要先去模拟（用户）登录，然后才能爬取数据

而模拟登录的过程，有时候或者经常，比后续的爬取数据更难，更复杂

所以此时的爬取全称是 先要模拟用户登录后再去爬取数据

也就常简称为 模拟登陆

用模拟登陆指代 爬虫

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:43

为何叫爬虫

为何叫爬虫

通过下面类比，来解释为何被叫做爬虫：

对比 | 现实世界的蜘蛛网 | 计算机世界的互联网
图解 | spider_net_insect | you_get_data
| 蜘蛛🕷 | 你👨‍💻‍自己
| 织网 = 用蜘蛛网 🕸 | 写爬虫代码📄=crawler=spider
| 捕获=抓自己要的东西=食物=昆虫🐞 | 爬取=抓包 自己要的数据🔢(并保存)

相关说明

互联网：是一个包含众多资源的大网络

狭义上说，主要指的是：

Web领域=各种网站=网页

里面有各种（我们想要爬取的）数据

比如想要爬取汽车的车型车系，可以从汽车之家等网站爬取

广义上说包含：

（上面提到的）各种网站=网页

各种app

包括各种Android和iOS中的app软件

比如想要爬取别人的app中的一些数据

比如爬取大众点评app中的商家和用户评论数据

各种其他渠道、终端的数据和资源

微信公众号

理论上也是属于网页

小程序

微信小程序

支付宝小程序

等等

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:17

爬虫应用领域

爬虫应用

爬虫应用领域

爬虫的应用领域，从广义上来说，人类用网络能做啥，爬虫就能干啥。

对于爬虫的具体应用领域，借用和爬虫密切相关的IP代理技术的应用来说明：

定时营销策略

游戏试玩

游戏挂机

QQ营销

宝贝收藏

下载补量

网购下单

使用软件自动抢票、抢购手机，提高效率

秒杀抢购

数据采集应用

爬虫抓取

页面采集

编写爬虫采集网络公开数据，分析得出全面的市场分析报告

投票点赞

投票发帖

网站注册

效果回访

网站搭建

优化推广方案

竞价优化

电商优化

邮件群发

打码投票

论坛发帖

问答推广

爬虫应用举例

自动登录=签到脚本

对于需要用户登录的网站或系统，如果用爬虫能够成功模拟登录的话，则往往就可以实现，其他一些人所需要的功能：

用来自动登录系统 -》 叫做 自动登录

用途有很多

比如

每天用来签到 -》 爬虫签到 = 爬虫签到脚本

比如

有人弄过 百度贴吧的签到脚本

自制BILIBILI弹幕爬取，签到，抢楼等爬虫

有人用来 用爬虫在各大机场自动签到获取流量

每天理财网站登陆签到获取积分

浦发信用卡自动签到

总之还是那句话

可以用爬虫来干什么

取决于你的想象：你想用来干什么

和你自己的技术水平：你自己能不能搞定，能不能实现

模拟用户发布内容

就变成了

自动发帖（脚本）

自动回复（脚本）

模拟浏览器操作

既然爬虫可以爬取网页，那么理论上就支持，用来模拟用户去点击网页，去实现模拟用户的操作，用爬虫，也叫做自动化脚本，去模拟浏览器的各种操作。

而对于模拟浏览器操作方面，和爬虫关系很密切，不过又属于不同的领域。

模拟浏览器=Web Browser Automation，这个领域，又有很多不同的框架，工具，和技术。

比如：

Selenium

Selenium知识总结

无头浏览器=Headless Browser：

含义：没有头的，没有界面的浏览器（内核）

-》 因为写代码控制和操作浏览器时，往往不需要（像普通用户用浏览器看网页时那样）看界面

-》 专门用于模拟浏览器行为，用于模拟浏览器，提供接口供你操作浏览器

常见工具

Phantomjs

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:40

爬虫的核心逻辑

爬虫的核心逻辑

整体概览和技术路线图

爬虫的核心逻辑和整体结构和技术路线图，可以通过 一图胜千言 来表示：

在线浏览

Spider Roadmap 20200714 - ProcessOn

图

核心逻辑和原理

接下来的文字解释，是对上述核心逻辑的详细诠释：

爬虫的最核心的逻辑和原理，主要包含：

爬虫的核心流程

先要抓包分析

再去写爬虫代码

爬虫的核心步骤=核心功能：写爬虫之前，要搞懂爬虫主要做了哪些事情

下载

提取

保存

爬虫的典型实现方式：然后再去搞懂有哪些方式去实现你的爬虫

裸写代码

用库写

用框架写

再去搞懂：

为何要用框架

以及有哪些语言的哪些爬虫框架

然后才是选用合适的语言的合适的爬虫框架，去基于框架写爬虫代码

下面详细解释。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:58:17

爬虫的核心流程

爬虫的核心流程

如前面所说，爬虫的最核心的流程，其实就是2个阶段：

先要抓包分析

搞清楚，网站中有哪些url网址的网页需要去抓取，app中有哪些页面背后对应着哪些api接口需要去抓取

以及每个url或api中，Request中都需要传递哪些参数，比如GET请求有哪些query parameter，POST有哪些Body的Json参数

才能返回正确的，期望的Response，才能获取到自己要的数据

如此，搞懂要抓取哪些数据，用什么逻辑才能获取到这些数据，是写代码真正实现这些逻辑，获取到真正的数据的前提和基础

再去写爬虫代码

然后才能根据前面已经搞懂的从无到有如何抓取到你要的数据的逻辑

选择合适的方式，是裸写代码，还是用库实现，还是用爬虫框架

去根据对应情况，写代码去下载页面或数据，再去裸写代码找合适的库实现规则去提取要的数据，最终保存数据

下面接着去详细解释，如何抓包分析，以及如何写爬虫代码。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:58:08

爬虫的核心步骤

爬虫的核心步骤

接下来介绍爬虫的原理、过程和步骤，以及相关涉及到的知识。

从原理上来说，写爬虫去爬数据的过程，最核心的就这3步：

下载=download

提取=extract

保存=save

下面详细解释每一步的各种细节：

下载=下载网页

做了什么：请求网址或api接口，去下载返回

得到什么：html网页或json字符串

涉及到

（尤其是新手需要学习）Http基本知识

Request

Method

GET

POST

等

Header

User-Agent

Content-Type

Accept

Authorization

等

Cookie

Body

data

json

Response

Status Code

Header

Cookie

Body

data

json

教程：主流数据格式：JSON

教程：HTTP知识总结

如果被爬方（网站，app等）

需要用户登录后才能看到数据

用技术绕过限制

模拟登陆

先要抓包分析出登录逻辑

再用代码模拟用户登录

做了一些反爬措施

验证码

用技术绕过限制

验证码识别

（用第三方）打码平台

IP限制 + 抓取频率限制

用技术绕过限制

IP代理池

设置抓取的间隔时间

身份限制

Http的Headers

UA=User-Agent

被爬网站所含页面层级很多

抓取策略

深度优先遍历策略

宽度优先遍历策略

反向链接数策略

Partial PageRank策略

OPIC策略策略

大站优先策略

提取数据

做了什么：从（返回的）网页（的html，js等）或json中提取

得到什么：自己需要的内容

涉及到

字符编码的问题

如果搞不清编码，就容易出现各种乱码问题

需要学习相关编码知识

教程：字符编码详解与应用

【整理Book】Python心得：字符串和字符编码

html的meta charset

编码检测

Python

chardet

如果被爬方做了反爬

数据加密

用技术绕过

找到解密的逻辑和方法

教程：安卓应用的安全和破解

【已解决】尝试破解小花生app安卓apk希望看到api返回的json中的J的解密算法得到明文

保存数据

做了什么：把数据保存到对应的地方

得到什么：包含了我们要的特定格式的数据的文件或数据库

保存成不同格式：

文件

txt

csv / excel

教程：Python心得：操作CSV和Excel

数据库

mysql

教程：主流关系数据库：MySQL

mongodb

教程：主流文档型数据库：MongoDB

sqlite

等等

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:58:12

爬虫的典型实现方式

爬虫的典型实现方式

实现爬虫的方式有很多，根据复杂度，可以分成典型的3种方式：

裸写爬虫代码

解释：在自己了解HTTP、爬虫等相关背景知识的前提下，用相对比较原始的方式，用内置库实现爬虫全部功能

优点：更贴近和了解底层技术

缺点：要求熟悉底层技术，相对用已有的库，写起来比较复杂

用第三方库写爬虫代码

解释：用第三方的、更强大、更好用，网络库下载内容，内容提取库提取数据

优点：省心，高效

缺点：

要额外引入库，且要了解如何使用

对于新手，往往是直接用了第三方库后，不了解内部机制

用爬虫框架

用成熟的也更复杂和强大的爬虫框架，让框架帮你做重复工作，自己只需学核心的爬虫逻辑即可爬取到数据

优点：适合更复杂的爬虫任务，充分利用框架的任务调度，url去重等等高级功能

缺点：

很多适合杀鸡用牛刀，比较重，不够轻量级

出了问题，需要熟悉内部机制才容易解决问题

下面详细解释，对于爬虫的核心步骤中，不同实现方式的优缺点和所涉及内容：

下载

裸写爬虫代码

举例

Python的urllib

C#的HttpWebRequest + HttpWebResponse

crifanLib.cs之Http

用第三方库写爬虫代码

举例

Python

requests

提取

提取数据的方式：

从json中提取想要的内容

用json库，把json字符串转换为json对象（dict，字典）即可

无需（html）解析相关的库

常见的库

Python

json

C#

Newtonsoft.Json

JavaScriptSerializer

从html，js等内容中提取想要的内容

裸写爬虫代码

正则

应用广泛的超强搜索：正则表达式

Python

re模块

Python中的正则表达式：re模块详解

XPath

XPath知识总结

用第三方库写爬虫代码

Python

lxml

【记录】Python中尝试用lxml去解析html – 在路上

BeautifulSoup

网页解析利器：BeautifulSoup

PyQuery

HTML解析库Python版jQuery：PyQuery

python-goose

等

C#

HTML解析

HtmlAgilityPack

sgml

Java

Jsoup

保存

裸写爬虫代码

自己写代码保存到对应文件或数据库中

用库写爬虫代码

用库去将数据保存到文件或数据库中

用爬虫框架

框架内置接口

PySpider

用内置接口，自动保存数据到对应数据库中

不同实现方式和爬虫不同步骤的对应关系

下面以Python语言为例，来解释不用爬虫的实现方式和不同步骤之间的对应关系：

| 下载（网页） | 提取（内容） | 保存（数据）
自己裸写Python代码 | urllib | re | txt/csv
用各种Python库组合 | requests | BeautifulSoup/lxml | csv / pymysql
用框架PySpider | requests (PySpider的self.crawl) | (PySpider内置的)PyQuery | (PySpider内置)各种数据库(接口）

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:58:25

抓包分析

抓包分析

抓包=抓包分析

什么是抓包

不论用哪种方式去写爬虫代码，对于下载来说，具体要请求网站url是什么，调用什么api接口，传递什么参数，以及获取到数据后，用什么规则提取出需要的数据等等内容，都需要事先去分析和研究清楚，这个抓取网络请求的数据包的过程，一般叫做：抓包

即：

下载

需要访问的网页url地址或api接口是什么

以及传递什么参数

提取

对于返回数据，需要抓取具体哪一部分

对应的数据的提取规则是什么

提示：

虽然对于爬虫的核心流程是先要抓包分析搞清楚逻辑，然后才能去写爬虫代码，不过实际上很多时候，是边分析，边写代码的。

尤其是对于一些复杂的网站或app来说，往往是分析的同时，也要写一些代码去验证和测试抓取的逻辑是否行得通的。

总之，对于爬虫的流程：

逻辑上是：先抓包分析，再写爬虫代码

实际上（往往）是：边抓包分析，边写代码

抓包的难度

普通网页：抓包分析，一般比较简单

复杂网站：对于需要登录才能获取到数据，且加了验证码等做了其他反爬措施和手段的网站和app，抓包分析起来，一般都很复杂

复杂网站的抓包分析和破解，往往比（之后的，单纯的）写爬虫去 下载+提取+保存，要难多了

抓包常用工具

我们要写爬虫去爬取的数据，从数据源的形态分，大概分两类：

网站=网页=网站中的各种网页

app=app中内部发出的请求设计的api接口

根据要抓取的数据的源不同，常用的一些辅助分析工具有：

网站抓包分析

Chrome的开发者工具

快捷键：

Windows: Ctrl + Shift + I

Mac: Command + Option + I

如何使用

官网资料：Chrome 开发者工具

IE的F12

如何使用

【整理】各种浏览器中的开发人员工具Developer Tools：IE9的F12，Chrome的Ctrl+Shift+J，Firefox的Firebug

【总结】浏览器中的开发人员工具（IE9的F12和Chrome的Ctrl+Shift+I）-网页分析的利器

【教程】如何利用IE9的F12去分析网站登陆过程中的复杂的（参数，cookie等）值（的来源）

【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程

Firefox的firebug

app抓包分析

Charles

教程：app抓包利器：Charles

通用工具

Wireshark

Postman

用于对于api去设置参数并发送请求测试是否能获取数据

教程：API开发利器：Postman

Fiddler

HttpWatcher

具体怎么抓包

先要搞清楚自己想要抓取什么数据，然后再去用工具辅助分析出网页或app等数据源中，如何一步步的获取对应数据，找到期间所要依次访问哪些url或api，传递什么参数，最终获取到所要的数据。

下面就来用实际例子来说明如何抓包。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:58:04

抓包分析网页

抓包分析网页

对于如何抓包分析网站网页类的内容，下面用具体例子来详细解释。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:57:13

静态网页

静态网页

以抓取汽车之家中车型车型数据为例解释如何抓包

下面就以，想要抓取汽车之家网站中的车型车系数据为例，来解释，如何用抓包工具辅助分析，依次访问哪些页面，之后如何提取，才能得到我们要的数据。

具体过程详见：

PySpider案例 · Python爬虫框架：PySpider

汽车之家的品牌等数据

汽车之家的车型详细数据

过程

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2021-09-18 14:08:05

动态网页

动态网页

动态网页

是相对于静态网页来说的

指的是，通过爬虫抓取代码得到的源码，往往只是（用户通过浏览器看到的全部内容的）其中一部分

剩下的内容，需要动态加载

往往需要我们额外再发出请求获取对应内容

前提是需要调试分析网页内容加载的逻辑

相关内容：

【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程

【记录】模拟登陆google

【教程】如何抓取动态网页内容

【教程】以抓取网易博客帖子中的最近读者信息为例，手把手教你如何抓取动态网页中的内容

TODO：

找个，需要登录的网站，或者是是网页内容需要后续执行js才能加载的例子，再去用抓包工具模拟登录，或分析数据是如何加载的。

举例：Chrome分析大众点评某页面获取店铺数据后用PySpider实现代码并下载数据

此次通过举例来说明，如何：

用Chrome分析逻辑后

再去（PySpider的）代码实现

期间要注意的是：要一点点模拟各种参数，才能获取到数据，否则会出现各种错误

要分析的网址：

http://www.dianping.com/suzhou/ch70/g27762

用Chrome打开后，是可以获取到数据的：

但是PySpider中，用代码：

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2019-04-15 14:56:12
# Project: DianpingChilrenEnglish

from pyspider.libs.base_handler import *

import os
import json
import codecs
import base64
import gzip
import copy
import time
import re
import csv
# import datetime
from datetime import datetime, timedelta

######################################################################
# Const
######################################################################
...
constCityListNamePattern = "cityList_%s_%s.json"
constMainCityFilename = "mainCityWithLevelList.json"

constUserAgentMacChrome = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"

gHost = "http://www.dianping.com"

CategoryLevel1ParentChild = "ch70" # 全部分类->亲子
CategoryLevel2ChildEnglish = "g27762" # 幼儿教育 -> 幼儿外语

######################################################################
# Project Specific Functions
######################################################################

######################################################################
# Main
######################################################################

class Handler(BaseHandler):
    crawl_config = {
        "connect_timeout": 100,
        "timeout": 600,
        "retries": 15,
        "headers": {
            "User-Agent": constUserAgentMacChrome,
            "Accept": "application/json, text/javascript, */*; q=0.01",
            "Content-Type": "application/json",
            "Origin": "http://www.dianping.com",
            # "X-Requested-With": "XMLHttpRequest",
        }
    }

    def on_start(self):
        # self.init()

        self.realStart()

    def realStart(self):
        ...
        # for debug
        """
        eachMainCity={'cityAbbrCode': 'SUZ', 'cityAreaCode': '0512', 'cityEnName': 'suzhou', 'cityId': 6, 'cityLevel': 2, 'cityName': '苏州', 'cityOrderId': 4888, 'cityPyName': 'suzhou', 'gLat': 31.297779, 'gLng': 120.585586, 'provinceId': 10, 'currentNodeLevel': 2, 'totalRank': '9', 'cityLevelFloat': 1.5, 'provinceName': '江苏省'}
        cityEnName=suzhou
        childEnglishEntryUrl=http://www.dianping.com/suzhou/ch70/g27762
        """
        childEnglishEntryUrl = "http://www.dianping.com/suzhou/ch70/g27762"
        self.crawl(
            childEnglishEntryUrl,
            callback=self.childEnglishCallback
        )

    def childEnglishCallback(self, response):
        respUrl = response.url
        print("respUrl=%s" % respUrl)
        respText = response.text
        print("respText=%s" % respText)

        # <ul class="shop-list">
        #   <li data-shopid="129412093" data-view="1">
        for eachShop in response.doc('ul[class="shop-list"] li[data-shopid]').items():
            print("eachShop=%s" % eachShop)

结果返回错误信息：

respText={"customData":{"requestCode":"c9847c945a1440d49460df748b757250","verifyUrl":"https://optimus-mtsi.meituan.com/optimus/verify?request_code=c9847c945a1440d49460df748b757250","imageUrl":"https://verify.meituan.com/v2/captcha?action=spiderindefence&request_code=c9847c945a1440d49460df748b757250","verifyPageUrl":"https://verify.meituan.com/v2/app/general_page?action=spiderindefence&requestCode=c9847c945a1440d49460df748b757250&platform=1000&adaptor=auto&succCallbackUrl=https://optimus-mtsi.meituan.com/optimus/verifyResult"},"code":406,"msg":"您的网络好像不太给力，请稍后再试"}

然后去参考Chromet中看到的参数：

对于各种header，想办法一个个加上去试试，最终发现是：

给Accept加上text/html相关类型：

# "Accept": "application/json, text/javascript, */*; q=0.01",
"Accept": "application/json, text/javascript, text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*; q=0.8,application/signed-exchange;v=b3",

就可以获取到数据了：

-》还是之前提到的哪个逻辑：想办法参考Chrome调试看到的所有的重要参数：

url中的query string

header中有价值的部分

甚至相关的cookie，session，localStorage等内容

都加上后，多数情况下，就可以获取到对应的返回的数据了。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2021-09-18 14:02:59

抓包分析app

抓包分析app

具体操作步骤，详见教程：

如何抓包app · app抓包利器：Charles

其他相关内容：

【记录】Mac中用Charles去抓包Android中的app家长通中的绘本

【基本解决】爬取app数据少儿流利说

【记录】爬取小花生app中自主阅读馆和亲子阅读馆中的有音频的绘本数据

【整理】Mac中用Charles抓包iOS或Android手机app中包括https的数据

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2021-09-18 14:04:41

爬虫框架

爬虫框架

而上面的三个步骤：下载+提取+保存，其中包含很多通用的，重复的逻辑和操作，所以有些人开发出来，独立的爬虫框架，方便我们去实现爬虫。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:10

为何需要爬虫框架

为何需要爬虫框架

接着来解释，为何要用爬虫框架：

框架帮你把大部分重复的工作都实现了

做了哪些通用的事情

下载

网络异常时自动重试retry

还可以设置

最大重试次数：最多重试几次

如果还是不行，才视为下载失败

重试间隔：两次重试之间的间隔时间

好处：不用担心偶尔某次网络有问题，就导致下载失败了，因为还可以自动重试

对比：自己裸写代码，就要考虑这种异常情况，导致自己爬虫代码臃肿和逻辑复杂

花了太多精力在和爬取数据关系不大的方面，不值得，效率低

下载进程的管理

同时发出多个url请求去下载内容

有专门的进程管理和调度策略

好处：能同时并发多个请求

对比：自己裸写代码去下载，往往同一时刻只能有一个请求

否则就要花很多精力去实现并发

url去重

前后（不同页面，不同场景下）发出的多个url中是否有重复的

如果有，则自动忽略掉，去掉，去除重复=去重

提取

做了啥

内置常用的内容提取的库

PySpider集成PyQuery

Scrapy集成选择器，支持：xpath、css、re

同时支持可选的第三方的库

Scrapy也支持用BeautifulSoup提取内容

好处：不用额外安装和使用这些库

对比：自己裸写代码就要考虑选用哪些合适的库去提取内容

保存

做了啥

集成各种保存数据的接口和框架

PySpider

自带默认保存为sqlite中

可以从界面中导出csv或json

其他数据库接口

mysql

mongodb

等等

好处：可以方便的选择保存数据的方式，无需过多操心细节

对比：自己裸写代码，还要安装不同的数据库的库，再手动写（sql等）代码去保存数据

还带了很多额外的好用功能

PySpider

带UI界面，调试非常方便

支持网页内容是执行js后才生成的

通过集成第三方phantomjs

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:13

常见爬虫框架

常见爬虫框架

Python

PySpider

Python爬虫框架：PySpider

Scrapy

主流Python框架：Scrapy

其他

Grab

Portia

newspaper

ruia

Cola

Sasila

Java

Nutch

Nutch是一个基于Apache的Lucene，类似Google的完整网络搜索引擎解决方案，基于Hadoop的分布式处理模型保证了系统的性能，类似Eclipse的插件机制保证了系统的可客户化，而且很容易集成到自己的应用之中。

Heritrix

Heritrix是一个开源，可扩展的web爬虫项目。Heritrix设计成严格按照robots.txt文件的排除指示和META robots标签。

crawler4j

crawler4j is an open source web crawler for Java which provides a simple interface for crawling the Web. Using it, you can setup a multi-threaded web crawler in few minutes

WebMagic

国人黄亿华先生的良心大作。无须配置、便于二次开发的爬虫框架，它提供简单灵活的API，只需少量代码即可实现一个爬虫

Golang

Pholcus

Colly

NodeJS

headless-chrome-crawler

C#

abot

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:00:05

如何写爬虫

如何写爬虫

由于不同语言对于爬虫的生态不同，导致选择不同语言去实现同样的爬虫目标，难度也差异很大。

类比来解释：

目的：爬虫

就像 要建一个自己喜欢的房子

不同语言难度

Python

已有：

建房子：给你建好了房子，且还搞好了硬装，有了基本的家具等

爬虫

生态极其丰富和强大

单独的库

下载

网络库

requests

提取

正则re

xpath

BeautifulSoup

PyQuery

等

爬虫框架

Scrapy

PySpider

等

需要你：

建房子：简单弄弄软装，就可以得到你喜欢的房子

爬虫

只需要分析网页内容加载逻辑，搞清楚规则

再去用Python代码实现这些规则和逻辑，即可

其他语言

已有

建房子

不仅没硬装，往往也没房子

甚至连钢筋水泥和沙子等原材料都没有

爬虫

不同的语言，对于网络相关库、解析相关库等等，都有一定程度的支持

但往往不够好，具体情况差异很大

对于其他编程语言，用来开发爬虫的生态和便利性，个人总体感觉是：

C# > Go > Java > PHP

需要你：

建房子

需要你从头做起去建房子

自己开车采购钢筋水泥沙子等原材料

然后找人开始建房子

把房子建好后

才能开始硬装

最后软装

做出你喜欢的房子

爬虫

很多时候都没有很好的轮子可用，导致爬虫方面开发效率很低

往往要了解http网络库的机制，甚至自己裸写代码，才能实现http层面的网页源码的下载

然后再去解析源码，期间甚至还要关注html字符编码的问题，才能提取出你要的内容

旧教程

之前写过一些旧教程，需要的也可以参考：

详解抓取网站，模拟登陆，抓取动态网页的原理和实现（Python，C#等）

如何用Python，C#等语言去实现抓取静态网页+抓取动态网页+模拟登陆网站 – 在路上

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:59:07

用Python写爬虫

用Python写爬虫

爬虫领域中，相对最方便和好用的编程语言（和库、框架等），要属：Python

具体如何做，详见独立教程：

如何用Python写爬虫

以及其他一些相关内容：

【教程】模拟登陆网站 之 Python版（内含两种版本的完整的可运行的代码）

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:59:28

用C#写爬虫

用C#写爬虫

C#中和爬虫相关的内置库有：

网络相关

System.Net

System.IO

内容提取解析

正则

System.Text.RegularExpressions

C#爬虫举例

之前整理过一些C#的爬虫方面的例子。整理于此供参考。

其中后续章节会专门介绍：

C#模拟登录百度首页

C#提取songtaste中h1user的值crifan

完整代码：

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Net;
using System.IO;
using System.Text.RegularExpressions;

namespace crawlWebsiteAndExtractInfo
{
    public partial class frmCrawlWebsite : Form
    {
        public frmCrawlWebsite()
        {
            InitializeComponent();
        }

        private void btnCrawlAndExtract_Click(object sender, EventArgs e)
        {
            //step1: get html from url
            //http://www.songtaste.com/user/351979/
            string urlToCrawl = txbUrlToCrawl.Text;
            //generate http request
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(urlToCrawl);
            //use GET method to get url's html
            req.Method = "GET";
            //use request to get response
            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
            string htmlCharset = "GBK";
            //use songtaste's html's charset GB2312 to decode html
            //otherwise will return messy code
            Encoding htmlEncoding = Encoding.GetEncoding(htmlCharset);
            StreamReader sr = new StreamReader(resp.GetResponseStream(), htmlEncoding);
            //read out the returned html
            string respHtml = sr.ReadToEnd();
            rtbExtractedHtml.Text = respHtml;
        }

        private void btnExtractInfo_Click(object sender, EventArgs e)
        {
            //step2: extract expected info
            //<h1 class="h1user">crifan</h1>
            string h1userP = @"<h1\s+class=""h1user"">(?<h1user>.+?)</h1>";
            Match foundH1user = (new Regex(h1userP)).Match(rtbExtractedHtml.Text);
            if (foundH1user.Success)
            {
                //extracted the expected h1user's value
                txbExtractedInfo.Text = foundH1user.Groups["h1user"].Value;
            }
            else
            {
                txbExtractedInfo.Text = "Not found h1 user !";
            }
        }

        private void lklTutorialUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            string tutorialUrl = "https://www.crifan.com/crawl_website_html_and_extract_info_using_csharp";
            System.Diagnostics.Process.Start(tutorialUrl);
        }
    }
}

运行效果截图：

完整代码（VS2010的项目）下载：

crawlWebsiteAndExtractInfo_csharp_2012-11-07.7z

具体细节详见：

【教程】抓取网并提取网页中所需要的信息 之 C#版

C#爬虫相关通用函数代码段

在折腾C#爬虫期间，把网络功能等常用函数和代码段，整理成独立函数：

crifanLib.cs

且额外加了文档说明：

crifanLib.cs之Http - 详解crifan的C#库：crifanLib.cs

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:32:56

C#模拟登录百度首页

C#模拟登录百度首页

之前用过C#实现模拟登陆百度首页，且有2个版本及对应的更新版本。

具体过程详见：

【教程】模拟登陆网站 之 C#版（内含两种版本的完整的可运行的代码）

下面整理如下：

C#模拟登陆百度首页之纯内置库版

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;

namespace emulateLoginBaidu
{
    public partial class frmEmulateLoginBaidu : Form
    {
        CookieCollection curCookies = null;

        bool gotCookieBaiduid, extractTokenValueOK, loginBaiduOk;

        public frmEmulateLoginBaidu()
        {
            InitializeComponent();
        }

        private void frmEmulateLoginBaidu_Load(object sender, EventArgs e)
        {
            //init
            curCookies = new CookieCollection();
            gotCookieBaiduid = false;
            extractTokenValueOK = false;
            loginBaiduOk = false;
        }

        /******************************************************************************
        functions in crifanLib.cs
        *******************************************************************************/

        //quote the input dict values
        //note: the return result for first para no '&'
        public string quoteParas(Dictionary<string, string> paras)
        {
            string quotedParas = "";
            bool isFirst = true;
            string val = "";
            foreach (string para in paras.Keys)
            {
                if (paras.TryGetValue(para, out val))
                {
                    if (isFirst)
                    {
                        isFirst = false;
                        quotedParas += para + "=" + HttpUtility.UrlPathEncode(val);
                    }
                    else
                    {
                        quotedParas += "&" + para + "=" + HttpUtility.UrlPathEncode(val);
                    }
                }
                else
                {
                    break;
                }
            }

            return quotedParas;
        }

        /******************************************************************************
        Demo emulate login baidu related functions
        *******************************************************************************/

        private void btnGetBaiduid_Click(object sender, EventArgs e)
        {
            //http://www.baidu.com/
            string baiduMainUrl = txbBaiduMainUrl.Text;
            //generate http request
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(baiduMainUrl);

            //add follow code to handle cookies
            req.CookieContainer = new CookieContainer();
            req.CookieContainer.Add(curCookies);

            req.Method = "GET";
            //use request to get response
            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
            txbGotBaiduid.Text = "";
            foreach (Cookie ck in resp.Cookies)
            {
                txbGotBaiduid.Text += "[" + ck.Name + "]=" + ck.Value;
                if (ck.Name == "BAIDUID")
                {
                    gotCookieBaiduid = true;
                }
            }

            if (gotCookieBaiduid)
            {
                //store cookies
                curCookies = resp.Cookies;
            }
            else
            {
                MessageBox.Show("错误：没有找到cookie BAIDUID ！");
            }
        }

        private void btnGetToken_Click(object sender, EventArgs e)
        {
            if (gotCookieBaiduid)
            {
                string getapiUrl = "https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true";
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(getapiUrl);

                //add previously got cookies
                req.CookieContainer = new CookieContainer();
                req.CookieContainer.Add(curCookies);

                req.Method = "GET";
                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
                StreamReader sr = new StreamReader(resp.GetResponseStream());
                string respHtml = sr.ReadToEnd();

                //bdPass.api.params.login_token='5ab690978812b0e7fbbe1bfc267b90b3';
                string tokenValP = @"bdPass\.api\.params\.login_token='(?<tokenVal>\w+)';";
                Match foundTokenVal = (new Regex(tokenValP)).Match(respHtml);
                if (foundTokenVal.Success)
                {
                    //extracted the token value
                    txbExtractedTokenVal.Text = foundTokenVal.Groups["tokenVal"].Value;
                    extractTokenValueOK = true;
                }
                else
                {
                    txbExtractedTokenVal.Text = "错误：没有找到token的值！";
                }

            }
            else
            {
                MessageBox.Show("错误：之前没有正确获得Cookie：BAIDUID ！");
            }
        }

        private void btnEmulateLoginBaidu_Click(object sender, EventArgs e)
        {
            if (gotCookieBaiduid && extractTokenValueOK)
            {
                string staticpage = "http://www.baidu.com/cache/user/html/jump.html";

                //init post dict info
                Dictionary<string, string> postDict = new Dictionary<string, string>();
                //postDict.Add("ppui_logintime", "");
                postDict.Add("charset", "utf-8");
                //postDict.Add("codestring", "");
                postDict.Add("token", txbExtractedTokenVal.Text);
                postDict.Add("isPhone", "false");
                postDict.Add("index", "0");
                //postDict.Add("u", "");
                //postDict.Add("safeflg", "0");
                postDict.Add("staticpage", staticpage);
                postDict.Add("loginType", "1");
                postDict.Add("tpl", "mn");
                postDict.Add("callback", "parent.bdPass.api.login._postCallback");
                postDict.Add("username", txbBaiduUsername.Text);
                postDict.Add("password", txbBaiduPassword.Text);
                //postDict.Add("verifycode", "");
                postDict.Add("mem_pass", "on");

                string baiduMainLoginUrl = "https://passport.baidu.com/v2/api/?login";
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(baiduMainLoginUrl);
                //add cookie
                req.CookieContainer = new CookieContainer();
                req.CookieContainer.Add(curCookies);
                //set to POST
                req.Method = "POST";
                req.ContentType = "application/x-www-form-urlencoded";
                //prepare post data
                string postDataStr = quoteParas(postDict);
                byte[] postBytes = Encoding.UTF8.GetBytes(postDataStr);
                req.ContentLength = postBytes.Length;
                //send post data
                Stream postDataStream = req.GetRequestStream();
                postDataStream.Write(postBytes, 0, postBytes.Length);
                postDataStream.Close();
                //got response
                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
                //got returned html
                StreamReader sr = new StreamReader(resp.GetResponseStream());
                string loginBaiduRespHtml = sr.ReadToEnd();

                //check whether got all expected cookies
                Dictionary<string, bool> cookieCheckDict = new Dictionary<string, bool>();
                string[] cookiesNameList = {"BDUSS", "PTOKEN", "STOKEN", "SAVEUSERID"};
                foreach (String cookieToCheck in cookiesNameList)
                {
                    cookieCheckDict.Add(cookieToCheck, false);
                }

                foreach (Cookie singleCookie in resp.Cookies)
                {
                    if (cookieCheckDict.ContainsKey(singleCookie.Name))
                    {
                        cookieCheckDict[singleCookie.Name] = true;
                    }
                }

                bool allCookiesFound = true;
                foreach (bool foundCurCookie in cookieCheckDict.Values)
                {
                    allCookiesFound = allCookiesFound && foundCurCookie;
                }

                loginBaiduOk = allCookiesFound;
                if (loginBaiduOk)
                {
                    txbEmulateLoginResult.Text = "成功模拟登陆百度首页！";
                }
                else
                {
                    txbEmulateLoginResult.Text = "模拟登陆百度首页 失败！";
                    txbEmulateLoginResult.Text += Environment.NewLine + "所返回的Header信息为：";
                    txbEmulateLoginResult.Text += Environment.NewLine + resp.Headers.ToString();
                    txbEmulateLoginResult.Text += Environment.NewLine + Environment.NewLine;
                    txbEmulateLoginResult.Text += Environment.NewLine + "所返回的HTML源码为：";
                    txbEmulateLoginResult.Text += Environment.NewLine + loginBaiduRespHtml;
                }
            }
            else
            {
                MessageBox.Show("错误：没有正确获得Cookie BAIDUID 和/或 没有正确提取出token值！");
            }
        }

        private void lklEmulateLoginTutorialUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            string emulateLoginTutorialUrl = "https://www.crifan.com/emulate_login_website_using_csharp";
            System.Diagnostics.Process.Start(emulateLoginTutorialUrl);
        }

        private void btnClearAll_Click(object sender, EventArgs e)
        {
            curCookies = new CookieCollection();
            gotCookieBaiduid = false;
            extractTokenValueOK = false;
            loginBaiduOk = false;

            txbGotBaiduid.Text = "";
            txbExtractedTokenVal.Text = "";

            txbBaiduUsername.Text = "";
            txbBaiduPassword.Text = "";
            txbEmulateLoginResult.Text = "";
        }
    }
}

模拟登录成功的效果截图：

完整的C#代码（VS2010项目）下载：

emulateLoginBaidu_csharp_2012-11-07.7z

以及后续针对.NET 4.0优化版本：

模拟百度登陆 独立完整代码版本 .NET 4.0

emulateLoginBaidu_csharp_independentCodeVersion_2013-09-11.7z

C#模拟登陆百度首页之crifanLib.cs版

完整代码：

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;

namespace emulateLoginBaidu
{
    public partial class frmEmulateLoginBaidu : Form
    {
        CookieCollection curCookies = null;

        bool gotCookieBaiduid, extractTokenValueOK, loginBaiduOk;

        public frmEmulateLoginBaidu()
        {
            InitializeComponent();
        }

        private void frmEmulateLoginBaidu_Load(object sender, EventArgs e)
        {
            this.AcceptButton = this.btnEmulateLoginBaidu;

            //init for crifanLib.cs
            curCookies = new CookieCollection();

            //init for demo login
            gotCookieBaiduid = false;
            extractTokenValueOK = false;
            loginBaiduOk = false;
        }

        /******************************************************************************
        functions in crifanLib.cs
        Online browser: http://code.google.com/p/crifanlib/source/browse/trunk/csharp/crifanLib.cs
        Download:       http://code.google.com/p/crifanlib/
        *******************************************************************************/

        //quote the input dict values
        //note: the return result for first para no '&'
        public string quoteParas(Dictionary<string, string> paras)
        {
            string quotedParas = "";
            bool isFirst = true;
            string val = "";
            foreach (string para in paras.Keys)
            {
                if (paras.TryGetValue(para, out val))
                {
                    if (isFirst)
                    {
                        isFirst = false;
                        quotedParas += para + "=" + HttpUtility.UrlPathEncode(val);
                    }
                    else
                    {
                        quotedParas += "&" + para + "=" + HttpUtility.UrlPathEncode(val);
                    }
                }
                else
                {
                    break;
                }
            }

            return quotedParas;
        }

        /*********************************************************************/
        /* cookie */
        /*********************************************************************/

        //add a single cookie to cookies, if already exist, update its value
        public void addCookieToCookies(Cookie toAdd, ref CookieCollection cookies, bool overwriteDomain)
        {
            bool found = false;

            if (cookies.Count > 0)
            {
                foreach (Cookie originalCookie in cookies)
                {
                    if (originalCookie.Name == toAdd.Name)
                    {
                        // !!! for different domain, cookie is not same,
                        // so should not set the cookie value here while their domains is not same
                        // only if it explictly need overwrite domain
                        if ((originalCookie.Domain == toAdd.Domain) ||
                            ((originalCookie.Domain != toAdd.Domain) && overwriteDomain))
                        {
                            //here can not force convert CookieCollection to HttpCookieCollection,
                            //then use .remove to remove this cookie then add
                            // so no good way to copy all field value
                            originalCookie.Value = toAdd.Value;

                            originalCookie.Domain = toAdd.Domain;

                            originalCookie.Expires = toAdd.Expires;
                            originalCookie.Version = toAdd.Version;
                            originalCookie.Path = toAdd.Path;

                            //following fields seems should not change
                            //originalCookie.HttpOnly = toAdd.HttpOnly;
                            //originalCookie.Secure = toAdd.Secure;

                            found = true;
                            break;
                        }
                    }
                }
            }

            if (!found)
            {
                if (toAdd.Domain != "")
                {
                    // if add the null domain, will lead to follow req.CookieContainer.Add(cookies) failed !!!
                    cookies.Add(toAdd);
                }
            }

        }//addCookieToCookies

        //add singel cookie to cookies, default no overwrite domain
        public void addCookieToCookies(Cookie toAdd, ref CookieCollection cookies)
        {
            addCookieToCookies(toAdd, ref cookies, false);
        }

        //check whether the cookies contains the ckToCheck cookie
        //support:
        //ckTocheck is Cookie/string
        //cookies is Cookie/string/CookieCollection/string[]
        public bool isContainCookie(object ckToCheck, object cookies)
        {
            bool isContain = false;

            if ((ckToCheck != null) && (cookies != null))
            {
                string ckName = "";
                Type type = ckToCheck.GetType();

                //string typeStr = ckType.ToString();

                //if (ckType.FullName == "System.string")
                if (type.Name.ToLower() == "string")
                {
                    ckName = (string)ckToCheck;
                }
                else if (type.Name == "Cookie")
                {
                    ckName = ((Cookie)ckToCheck).Name;
                }

                if (ckName != "")
                {
                    type = cookies.GetType();

                    // is single Cookie
                    if (type.Name == "Cookie")
                    {
                        if (ckName == ((Cookie)cookies).Name)
                        {
                            isContain = true;
                        }
                    }
                    // is CookieCollection
                    else if (type.Name == "CookieCollection")
                    {
                        foreach (Cookie ck in (CookieCollection)cookies)
                        {
                            if (ckName == ck.Name)
                            {
                                isContain = true;
                                break;
                            }
                        }
                    }
                    // is single cookie name string
                    else if (type.Name.ToLower() == "string")
                    {
                        if (ckName == (string)cookies)
                        {
                            isContain = true;
                        }
                    }
                    // is cookie name string[]
                    else if (type.Name.ToLower() == "string[]")
                    {
                        foreach (string name in ((string[])cookies))
                        {
                            if (ckName == name)
                            {
                                isContain = true;
                                break;
                            }
                        }
                    }
                }
            }

            return isContain;
        }//isContainCookie

        // update cookiesToUpdate to localCookies
        // if omitUpdateCookies designated, then omit cookies of omitUpdateCookies in cookiesToUpdate
        public void updateLocalCookies(CookieCollection cookiesToUpdate, ref CookieCollection localCookies, object omitUpdateCookies)
        {
            if (cookiesToUpdate.Count > 0)
            {
                if (localCookies == null)
                {
                    localCookies = cookiesToUpdate;
                }
                else
                {
                    foreach (Cookie newCookie in cookiesToUpdate)
                    {
                        if (isContainCookie(newCookie, omitUpdateCookies))
                        {
                            // need omit process this
                        }
                        else
                        {
                            addCookieToCookies(newCookie, ref localCookies);
                        }
                    }
                }
            }
        }//updateLocalCookies

        //update cookiesToUpdate to localCookies
        public void updateLocalCookies(CookieCollection cookiesToUpdate, ref CookieCollection localCookies)
        {
            updateLocalCookies(cookiesToUpdate, ref localCookies, null);
        }

        /*********************************************************************/
        /* HTTP */
        /*********************************************************************/

        /* get url's response */
        public HttpWebResponse getUrlResponse(string url,
                                        Dictionary<string, string> headerDict,
                                        Dictionary<string, string> postDict,
                                        int timeout,
                                        string postDataStr)
        {
            //CookieCollection parsedCookies;

            HttpWebResponse resp = null;

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);

            req.AllowAutoRedirect = true;
            req.Accept = "*/*";

            //const string gAcceptLanguage = "en-US"; // zh-CN/en-US
            //req.Headers["Accept-Language"] = gAcceptLanguage;

            req.KeepAlive = true;

            //IE8
            //const string gUserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E";
            //IE9
            //const string gUserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"; // x64
            const string gUserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; // x86
            //Chrome
            //const string gUserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4";
            //Mozilla Firefox
            //const string gUserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6";
            req.UserAgent = gUserAgent;

            req.Headers["Accept-Encoding"] = "gzip, deflate";
            req.AutomaticDecompression = DecompressionMethods.GZip;

            req.Proxy = null;

            if (timeout > 0)
            {
                req.Timeout = timeout;
            }

            if (curCookies != null)
            {
                req.CookieContainer = new CookieContainer();
                req.CookieContainer.PerDomainCapacity = 40; // following will exceed max default 20 cookie per domain
                req.CookieContainer.Add(curCookies);
            }

            if (headerDict != null)
            {
                foreach (string header in headerDict.Keys)
                {
                    string headerValue = "";
                    if (headerDict.TryGetValue(header, out headerValue))
                    {
                        // following are allow the caller overwrite the default header setting
                        if (header.ToLower() == "referer")
                        {
                            req.Referer = headerValue;
                        }
                        else if (header.ToLower() == "allowautoredirect")
                        {
                            bool isAllow = false;
                            if (bool.TryParse(headerValue, out isAllow))
                            {
                                req.AllowAutoRedirect = isAllow;
                            }
                        }
                        else if (header.ToLower() == "accept")
                        {
                            req.Accept = headerValue;
                        }
                        else if (header.ToLower() == "keepalive")
                        {
                            bool isKeepAlive = false;
                            if (bool.TryParse(headerValue, out isKeepAlive))
                            {
                                req.KeepAlive = isKeepAlive;
                            }
                        }
                        else if (header.ToLower() == "accept-language")
                        {
                            req.Headers["Accept-Language"] = headerValue;
                        }
                        else if (header.ToLower() == "useragent")
                        {
                            req.UserAgent = headerValue;
                        }
                        else
                        {
                            req.Headers[header] = headerValue;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }

            if (postDict != null || postDataStr != "")
            {
                req.Method = "POST";
                req.ContentType = "application/x-www-form-urlencoded";

                if (postDict != null)
                {
                    postDataStr = quoteParas(postDict);
                }

                //byte[] postBytes = Encoding.GetEncoding("utf-8").GetBytes(postData);
                byte[] postBytes = Encoding.UTF8.GetBytes(postDataStr);
                req.ContentLength = postBytes.Length;

                Stream postDataStream = req.GetRequestStream();
                postDataStream.Write(postBytes, 0, postBytes.Length);
                postDataStream.Close();
            }
            else
            {
                req.Method = "GET";
            }

            //may timeout, has fixed in:
            //https://www.crifan.com/fixed_problem_sometime_httpwebrequest_getresponse_timeout/
            resp = (HttpWebResponse)req.GetResponse();

            updateLocalCookies(resp.Cookies, ref curCookies);

            return resp;
        }

        public HttpWebResponse getUrlResponse(string url,
                                    Dictionary<string, string> headerDict,
                                    Dictionary<string, string> postDict)
        {
            return getUrlResponse(url, headerDict, postDict, 0, "");
        }

        public HttpWebResponse getUrlResponse(string url)
        {
            return getUrlResponse(url, null, null, 0, "");
        }

        // valid charset:"GB18030"/"UTF-8", invliad:"UTF8"
        public string getUrlRespHtml(string url,
                                        Dictionary<string, string> headerDict,
                                        string charset,
                                        Dictionary<string, string> postDict,
                                        int timeout,
                                        string postDataStr)
        {
            string respHtml = "";

            //HttpWebResponse resp = getUrlResponse(url, headerDict, postDict, timeout);
            HttpWebResponse resp = getUrlResponse(url, headerDict, postDict, timeout, postDataStr);

            //long realRespLen = resp.ContentLength;

            StreamReader sr;
            if ((charset != null) && (charset != ""))
            {
                Encoding htmlEncoding = Encoding.GetEncoding(charset);
                sr = new StreamReader(resp.GetResponseStream(), htmlEncoding);
            }
            else
            {
                sr = new StreamReader(resp.GetResponseStream());
            }
            respHtml = sr.ReadToEnd();

            return respHtml;
        }

        public string getUrlRespHtml(string url, Dictionary<string, string> headerDict, string charset, Dictionary<string, string> postDict, string postDataStr)
        {
            return getUrlRespHtml(url, headerDict, charset, postDict, 0, postDataStr);
        }

        public string getUrlRespHtml(string url, Dictionary<string, string> headerDict, Dictionary<string, string> postDict)
        {
            return getUrlRespHtml(url, headerDict, "", postDict, "");
        }

        public string getUrlRespHtml(string url, Dictionary<string, string> headerDict)
        {
            return getUrlRespHtml(url, headerDict, null);
        }

        public string getUrlRespHtml(string url, string charset, int timeout)
        {
            return getUrlRespHtml(url, null, charset, null, timeout, "");
        }

        public string getUrlRespHtml(string url, string charset)
        {
            return getUrlRespHtml(url, charset, 0);
        }

        public string getUrlRespHtml(string url)
        {
            return getUrlRespHtml(url, "");
        }

        /******************************************************************************
        Demo emulate login baidu related functions
        *******************************************************************************/

        private void btnGetBaiduid_Click(object sender, EventArgs e)
        {
            //http://www.baidu.com/
            string baiduMainUrl = txbBaiduMainUrl.Text;
            HttpWebResponse resp = getUrlResponse(baiduMainUrl);
            txbGotBaiduid.Text = "";
            foreach (Cookie ck in resp.Cookies)
            {
                txbGotBaiduid.Text += "[" + ck.Name + "]=" + ck.Value;
                if (ck.Name == "BAIDUID")
                {
                    gotCookieBaiduid = true;
                }
            }

            if (gotCookieBaiduid)
            {
                //store cookies
                curCookies = resp.Cookies;
            }
            else
            {
                MessageBox.Show("错误：没有找到cookie BAIDUID ！");
            }
        }

        private void btnGetToken_Click(object sender, EventArgs e)
        {
            if (gotCookieBaiduid)
            {
                string getapiUrl = "https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true";
                string respHtml = getUrlRespHtml(getapiUrl);

                //bdPass.api.params.login_token='5ab690978812b0e7fbbe1bfc267b90b3';
                string tokenValP = @"bdPass\.api\.params\.login_token='(?<tokenVal>\w+)';";
                Match foundTokenVal = (new Regex(tokenValP)).Match(respHtml);
                if (foundTokenVal.Success)
                {
                    //extracted the token value
                    txbExtractedTokenVal.Text = foundTokenVal.Groups["tokenVal"].Value;
                    extractTokenValueOK = true;
                }
                else
                {
                    txbExtractedTokenVal.Text = "错误：没有找到token的值！";
                }

            }
            else
            {
                MessageBox.Show("错误：之前没有正确获得Cookie：BAIDUID ！");
            }
        }

        private void btnEmulateLoginBaidu_Click(object sender, EventArgs e)
        {
            if (gotCookieBaiduid && extractTokenValueOK)
            {
                string staticpage = "http://www.baidu.com/cache/user/html/jump.html";

                //init post dict info
                Dictionary<string, string> postDict = new Dictionary<string, string>();
                //postDict.Add("ppui_logintime", "");
                postDict.Add("charset", "utf-8");
                //postDict.Add("codestring", "");
                postDict.Add("token", txbExtractedTokenVal.Text);
                postDict.Add("isPhone", "false");
                postDict.Add("index", "0");
                //postDict.Add("u", "");
                //postDict.Add("safeflg", "0");
                postDict.Add("staticpage", staticpage);
                postDict.Add("loginType", "1");
                postDict.Add("tpl", "mn");
                postDict.Add("callback", "parent.bdPass.api.login._postCallback");
                postDict.Add("username", txbBaiduUsername.Text);
                postDict.Add("password", txbBaiduPassword.Text);
                //postDict.Add("verifycode", "");
                postDict.Add("mem_pass", "on");

                string baiduMainLoginUrl = "https://passport.baidu.com/v2/api/?login";
                string loginBaiduRespHtml = getUrlRespHtml(baiduMainLoginUrl, null, postDict);

                //check whether got all expected cookies
                Dictionary<string, bool> cookieCheckDict = new Dictionary<string, bool>();
                string[] cookiesNameList = {"BDUSS", "PTOKEN", "STOKEN", "SAVEUSERID"};
                foreach (String cookieToCheck in cookiesNameList)
                {
                    cookieCheckDict.Add(cookieToCheck, false);
                }

                foreach (Cookie singleCookie in curCookies)
                {
                    if (cookieCheckDict.ContainsKey(singleCookie.Name))
                    {
                        cookieCheckDict[singleCookie.Name] = true;
                    }
                }

                bool allCookiesFound = true;
                foreach (bool foundCurCookie in cookieCheckDict.Values)
                {
                    allCookiesFound = allCookiesFound && foundCurCookie;
                }

                loginBaiduOk = allCookiesFound;
                if (loginBaiduOk)
                {
                    txbEmulateLoginResult.Text = "成功模拟登陆百度首页！";
                }
                else
                {
                    txbEmulateLoginResult.Text = "模拟登陆百度首页 失败！";
                    txbEmulateLoginResult.Text += Environment.NewLine + "所返回的HTML源码为：";
                    txbEmulateLoginResult.Text += Environment.NewLine + loginBaiduRespHtml;
                }
            }
            else
            {
                MessageBox.Show("错误：没有正确获得Cookie BAIDUID 和/或 没有正确提取出token值！");
            }
        }

        private void lklEmulateLoginTutorialUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            string emulateLoginTutorialUrl = "https://www.crifan.com/emulate_login_website_using_csharp";
            System.Diagnostics.Process.Start(emulateLoginTutorialUrl);
        }

        private void btnClearAll_Click(object sender, EventArgs e)
        {
            curCookies = new CookieCollection();
            gotCookieBaiduid = false;
            extractTokenValueOK = false;
            loginBaiduOk = false;

            txbGotBaiduid.Text = "";
            txbExtractedTokenVal.Text = "";

            txbBaiduUsername.Text = "";
            txbBaiduPassword.Text = "";
            txbEmulateLoginResult.Text = "";
        }

    }
}

完整的C#代码（VS2010项目）下载：

emulateLoginBaidu_csharp_crifanLibVersion_2012-11-07.7z

以及后续针对.NET 4.0优化版本：

模拟百度登陆 （利用我自己的）crifanLib版本 .NET 4.0

emulateLoginBaidu_csharp_crifanLibVersion_2013-09-11.7z

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:35:18

用Go写爬虫

用Go写爬虫

Go中和爬虫相关的一些内置库有：

网络相关

net/http

net/http/cookiejar

net/url

内容提取

正则

regexp

数据处理

bytes

下面主要通过一个例子来介绍，如何用Go语言去实现爬虫。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:31:17

Go模拟登录百度首页

Go模拟登录百度首页

很久之前用Go实现了模拟登录百度

完整代码为：

/*
 * [File]
 * EmulateLoginBaidu.go
 *
 * [Function]
 * 【记录】用go语言实现模拟登陆百度
 * https://www.crifan.com/emulate_login_baidu_using_go_language/
 *
 * [Version]
 * 2013-09-21
 *
 * [Contact]
 * https://www.crifan.com/about/me/
 */
package main

import (
    "fmt"
    //"builtin"
    //"log"
    "os"
    "runtime"
    "path"
    "strings"
    "time"
    //"io"
    "io/ioutil"
    "net/http"
    "net/http/cookiejar"
    "net/url"
    //"sync"
    //"net/url"
    "regexp"
    //"bufio"
    "bytes"
)

//import l4g "log4go.googlecode.com/hg"
//import l4g "code.google.com/p/log4go"
import "code.google.com/p/log4go"

/***************************************************************************************************
    Global Variables
***************************************************************************************************/
var gCurCookies []*http.Cookie;
var gCurCookieJar *cookiejar.Jar;
var gLogger log4go.Logger;

/***************************************************************************************************
    Functions
***************************************************************************************************/
//do init before all others
func initAll(){
    gCurCookies = nil
    //var err error;
    gCurCookieJar,_ = cookiejar.New(nil)
    gLogger = nil

    initLogger()
    initCrifanLib()
}

//de-init for all
func deinitAll(){
    gCurCookies = nil
    if(nil == gLogger) {
        gLogger.Close();
        //os.Stdout.Sync() //try manually flush, but can not fix log4go's flush bug

        gLogger = nil
    }
}

//do some init for crifanLib
func initCrifanLib(){
    gLogger.Debug("init for crifanLib")
    gCurCookies = nil
    return
}

//init for logger
func initLogger(){
    var filenameOnly string = GetCurFilename()
    var logFilename string =  filenameOnly + ".log";

    //gLogger = log4go.NewLogger()
    //gLogger = make(log4go.Logger)

    //for console
    //gLogger.AddFilter("stdout", log4go.INFO, log4go.NewConsoleLogWriter())
    gLogger = log4go.NewDefaultLogger(log4go.INFO)

    //for log file
    if _, err := os.Stat(logFilename); err == nil {
        //fmt.Printf("found old log file %s, now remove it\n", logFilename)
        os.Remove(logFilename)
    }
    //gLogger.AddFilter("logfile", log4go.FINEST, log4go.NewFileLogWriter(logFilename, true))
    //gLogger.AddFilter("logfile", log4go.FINEST, log4go.NewFileLogWriter(logFilename, false))
    gLogger.AddFilter("log", log4go.FINEST, log4go.NewFileLogWriter(logFilename, false))
    gLogger.Debug("Current time is : %s", time.Now().Format("15:04:05 MST 2006/01/02"))

    return
}

// GetCurFilename
// Get current file name, without suffix
func GetCurFilename() string {
    _, fulleFilename, _, _ := runtime.Caller(0)
    //fmt.Println(fulleFilename)
    var filenameWithSuffix string
    filenameWithSuffix = path.Base(fulleFilename)
    //fmt.Println("filenameWithSuffix=", filenameWithSuffix)
    var fileSuffix string
    fileSuffix = path.Ext(filenameWithSuffix)
    //fmt.Println("fileSuffix=", fileSuffix)

    var filenameOnly string
    filenameOnly = strings.TrimSuffix(filenameWithSuffix, fileSuffix)
    //fmt.Println("filenameOnly=", filenameOnly)

    return filenameOnly
}

//get url response html
func getUrlRespHtml(strUrl string, postDict map[string]string) string{
    gLogger.Debug("in getUrlRespHtml, strUrl=%s", strUrl)
    gLogger.Debug("postDict=%s", postDict)

    var respHtml string = "";

    httpClient := &http.Client{
        //Transport:nil,
        //CheckRedirect: nil,
        Jar:gCurCookieJar,
    }

    var httpReq *http.Request
    //var newReqErr error
    if nil == postDict {
        gLogger.Debug("is GET")
        //httpReq, newReqErr = http.NewRequest("GET", strUrl, nil)
        httpReq, _ = http.NewRequest("GET", strUrl, nil)
        // ...
        //httpReq.Header.Add("If-None-Match", `W/"wyzzy"`)
    } else {
        //【记录】go语言中实现http的POST且传递对应的post data
        //https://www.crifan.com/go_language_http_do_post_pass_post_data
        gLogger.Debug("is POST")
        postValues := url.Values{}
        for postKey, PostValue := range postDict{
            postValues.Set(postKey, PostValue)
        }
        gLogger.Debug("postValues=%s", postValues)
        postDataStr := postValues.Encode()
        gLogger.Debug("postDataStr=%s", postDataStr)
        postDataBytes := []byte(postDataStr)
        gLogger.Debug("postDataBytes=%s", postDataBytes)
        postBytesReader := bytes.NewReader(postDataBytes)
        //httpReq, newReqErr = http.NewRequest("POST", strUrl, postBytesReader)
        httpReq, _ = http.NewRequest("POST", strUrl, postBytesReader)
        //httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
        httpReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    }

    httpResp, err := httpClient.Do(httpReq)
    // ...

    //httpResp, err := http.Get(strUrl)
    //gLogger.Info("http.Get done")
    if err != nil {
        gLogger.Warn("http get strUrl=%s response error=%s\n", strUrl, err.Error())
    }
    gLogger.Debug("httpResp.Header=%s", httpResp.Header)
    gLogger.Debug("httpResp.Status=%s", httpResp.Status)

    defer httpResp.Body.Close()
    // gLogger.Info("defer httpResp.Body.Close done")

    body, errReadAll := ioutil.ReadAll(httpResp.Body)
    //gLogger.Info("ioutil.ReadAll done")
    if errReadAll != nil {
        gLogger.Warn("get response for strUrl=%s got error=%s\n", strUrl, errReadAll.Error())
    }
    //gLogger.Debug("body=%s\n", body)

    //gCurCookies = httpResp.Cookies()
    //gCurCookieJar = httpClient.Jar;
    gCurCookies = gCurCookieJar.Cookies(httpReq.URL);
    //gLogger.Info("httpResp.Cookies done")

    //respHtml = "just for test log ok or not"
    respHtml = string(body)
    //gLogger.Info("httpResp body []byte to string done")

    return respHtml
}

func dbgPrintCurCookies() {
    var cookieNum int = len(gCurCookies);
    gLogger.Debug("cookieNum=%d", cookieNum)
    for i := 0; i < cookieNum; i++ {
        var curCk *http.Cookie = gCurCookies[i];
        //gLogger.Debug("curCk.Raw=%s", curCk.Raw)
        gLogger.Debug("------ Cookie [%d]------", i)
        gLogger.Debug("Name\t\t=%s", curCk.Name)
        gLogger.Debug("Value\t=%s", curCk.Value)
        gLogger.Debug("Path\t\t=%s", curCk.Path)
        gLogger.Debug("Domain\t=%s", curCk.Domain)
        gLogger.Debug("Expires\t=%s", curCk.Expires)
        gLogger.Debug("RawExpires\t=%s", curCk.RawExpires)
        gLogger.Debug("MaxAge\t=%d", curCk.MaxAge)
        gLogger.Debug("Secure\t=%t", curCk.Secure)
        gLogger.Debug("HttpOnly\t=%t", curCk.HttpOnly)
        gLogger.Debug("Raw\t\t=%s", curCk.Raw)
        gLogger.Debug("Unparsed\t=%s", curCk.Unparsed)
    }
}

func main() {
    initAll()

    gLogger.Info("============ 程序说明 ============");
    gLogger.Info("功能：本程序是用来演示使用Java代码去实现模拟登陆百度");
    gLogger.Info("注意事项：部分百度账户，在登陆时会出现：");
    gLogger.Info("1.部分百度账户，在登陆时会出现：");
    gLogger.Info("系统检测到您的帐号疑似被盗，存在安全风险。请尽快修改密码。");
    gLogger.Info("此时，本程序，无法成功模拟登陆，请自行按照提示去修改密码后，就可以了。");

    //step1: access baidu url to get cookie BAIDUID
    gLogger.Info("====== 步骤1：获得BAIDUID的Cookie ======")
    var baiduMainUrl string = "http://www.baidu.com/";
    gLogger.Debug("baiduMainUrl=%s", baiduMainUrl)
    respHtml := getUrlRespHtml(baiduMainUrl, nil)
    gLogger.Debug("respHtml=%s", respHtml)
    dbgPrintCurCookies()

    //check cookie
    var bGotCookieBaiduid = false;
    //var cookieNameListToCheck []string = ["BAIDUID"]
    //toCheckCookieNameList := [1]string{"BAIDUID"}
    toCheckCookieNameList := []string{"BAIDUID"}
    toCheckCookieNum := len(toCheckCookieNameList)
    gLogger.Debug("toCheckCookieNum=%d", toCheckCookieNum)
    curCookieNum := len(gCurCookies)
    gLogger.Debug("curCookieNum=%d", curCookieNum)
    for i := 0; i < toCheckCookieNum; i++ {
        toCheckCkName := toCheckCookieNameList[i];
        gLogger.Debug("[%d]toCheckCkName=%s", i, toCheckCkName)
        for j := 0; j < curCookieNum; j++{
            curCookie := gCurCookies[j]
            if(strings.EqualFold(toCheckCkName, curCookie.Name)){
                bGotCookieBaiduid = true;
                break;
            }
        }
    }

    if bGotCookieBaiduid {
        gLogger.Info("Found cookie BAIDUID");
    }else{
        gLogger.Info("Not found cookie BAIDUID");
    }

    //step2: login, pass paras, extract resp cookie
    gLogger.Info("====== 步骤2：提取login_token ======");
    bExtractTokenValueOK := false
    strLoginToken := ""
    var getApiRespHtml string;
    if bGotCookieBaiduid{
        //https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true
        var getapiUrl string = "https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true";
        getApiRespHtml = getUrlRespHtml(getapiUrl, nil);
        gLogger.Debug("getApiRespHtml=%s", getApiRespHtml);
        dbgPrintCurCookies()

        //bdPass.api.params.login_token='278623fc5463aa25b0189ddd34165592';
        //use regex to extract login_token
        //【记录】go语言中用正则表达式查找某个值
        //https://www.crifan.com/go_language_regular_expression_find_value/
        loginTokenP, _ := regexp.Compile(`bdPass\.api\.params\.login_token='(?P<loginToken>\w+)';`)
        //loginToken := loginTokenP.FindString(getApiRespHtml);
        //loginToken := loginTokenP.FindSubmatch(getApiRespHtml);
        foundLoginToken := loginTokenP.FindStringSubmatch(getApiRespHtml);
        gLogger.Debug("foundLoginToken=%s", foundLoginToken);
        if nil != foundLoginToken {
            strLoginToken = foundLoginToken[1] //tmp go regexp not support named group, so use index here
            gLogger.Info("found bdPass.api.params.login_token=%s", strLoginToken);
            bExtractTokenValueOK = true;
        } else {
            gLogger.Warn(" not found login_token from html=%s", getApiRespHtml);
        }
    }

    //step3: verify returned cookies
    bLoginBaiduOk := false;
    if bGotCookieBaiduid && bExtractTokenValueOK {
        gLogger.Info("======步骤3：登陆百度并检验返回的Cookie ======");
        staticPageUrl := "http://www.baidu.com/cache/user/html/jump.html";

        postDict := map[string]string{}
        //postDict["ppui_logintime"] = ""
        postDict["charset"] = "utf-8"
        //postDict["codestring"] = ""
        postDict["token"] = strLoginToken
        postDict["isPhone"] = "false"
        postDict["index"] = "0"
        //postDict["u"] = ""
        //postDict["safeflg"] = "0"
        postDict["staticpage"] = staticPageUrl
        postDict["loginType"] = "1"
        postDict["tpl"] = "mn"
        postDict["callback"] = "parent.bdPass.api.login._postCallback"

        //【已解决】go语言中获得控制台输入的字符串
        //https://www.crifan.com/go_language_get_console_input_string/
        strBaiduUsername := ""
        strBaiduPassword := ""
        gLogger.Info("Plese input:")
        gLogger.Info("Baidu Username:")
        _, err1 := fmt.Scanln(&strBaiduUsername)
        if nil == err1 {
            gLogger.Debug("strBaiduUsername=%s", strBaiduUsername)
        }
        gLogger.Info("Baidu Password:")
        _, err2 := fmt.Scanln(&strBaiduPassword)
        if nil == err2 {
            gLogger.Debug("strBaiduPassword=%s", strBaiduPassword)
        }

        postDict["username"] = strBaiduUsername
        postDict["password"] = strBaiduPassword
        postDict["verifycode"] = ""
        postDict["mem_pass"] = "on"

        gLogger.Debug("postDict=%s", postDict)

        baiduMainLoginUrl := "https://passport.baidu.com/v2/api/?login";
        loginBaiduRespHtml := getUrlRespHtml(baiduMainLoginUrl, postDict);
        gLogger.Debug("loginBaiduRespHtml=%s", loginBaiduRespHtml)
        dbgPrintCurCookies();

        //check resp cookies exist or not
        cookieNameDict := map[string]bool{
            "BDUSS"     : false,
            "PTOKEN"    : false,
            "STOKEN"    : false,
            //"SAVEUSERID": false, //be deleted
        }

        for cookieName, _ := range cookieNameDict {
            for _, singleCookie := range gCurCookies {
                //if(strings.EqualFold(cookieName, singleCookie.Name)){
                if cookieName == singleCookie.Name {
                    cookieNameDict[cookieName] = true;
                    gLogger.Debug("Found cookie %s", cookieName)
                }
            }
        }
        gLogger.Debug("After check resp cookie, cookieNameDict=%s", cookieNameDict)

        bAllCookiesFound := true
        for _, bIsExist := range cookieNameDict {
            bAllCookiesFound = bAllCookiesFound && bIsExist
        }
        bLoginBaiduOk = bAllCookiesFound
        if (bLoginBaiduOk) {
            gLogger.Info("成功模拟登陆百度首页！" );
        } else{
            gLogger.Info("模拟登陆百度首页 失败！");
            gLogger.Info("所返回的HTML源码为：" + loginBaiduRespHtml);
        }
    }

    deinitAll()

    //【workaround】go语言中用log4go输出信息时有bug：只输出部分信息，甚至是无任何输出
    //https://www.crifan.com/go_language_log4go_only_output_part_info/
    time.Sleep(100 * time.Millisecond)
}

效果截图：

具体过程详见：

【记录】用go语言实现模拟登陆百度

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:58:37

用Java写爬虫

用Java写爬虫

Java中用来爬虫开发相关的一些内置库有：

HTTP网络库

org.apache.http

HttpResponse

NameValuePair

cookie

Cookie

message

BasicNameValuePair

内容解析

正则

java.util.regex

Matcher

Pattern

下面主要通过例子来介绍如何用Java实现爬虫。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:31:34

Java模拟登录百度首页

Java模拟登录百度首页

很早之前用过Java实现了模拟百度登录。

主体代码是：

/**
 * [File]
 * EmulateLoginBaidu.java
 *
 * [Function]
 * Use Java code to emulate login baidu
 *
 * 【教程】模拟登陆百度之Java代码版
 * https://www.crifan.com/emulate_login_baidu_use_java_code
 *
 * [Version]
 * v1.0, 2013-09-17
 *
 * [Note]
 * 1. need add apache http lib:
 * 【已解决】Eclipse的java代码出错：The import org.apache cannot be resolved
 * https://www.crifan.com/java_eclipse_the_import_org_apache_cannot_be_resolved/
 * 2.need crifanLib.java
 * http://code.google.com/p/crifanlib/source/browse/trunk/java/crifanLib.java
 *
 * [History]
 * [v1.0]
 * 1. initial version, finally successfully emulate login baidu using java code.
 */

//import java.io.IOException;
import java.util.ArrayList;
//import java.util.Calendar;
//import java.util.Date;
//import java.util.GregorianCalendar;
import java.util.HashMap;
//import java.util.Hashtable;
import java.util.List;
//import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.cookie.Cookie;
//import org.apache.http.impl.cookie.BasicClientCookie;
//import org.apache.http.impl.cookie.BasicClientCookie2;
import org.apache.http.message.BasicNameValuePair;

//import crifanLib;

/**
 * @author CLi
 *
 */
public class EmulateLoginBaidu {
    static crifanLib crl;

    /**
     * @param args
     */
    public static void main(String[] args) {
        crl = new crifanLib();

        // TODO Auto-generated method stub
        EmulateLoginBaiduUsingJava();
    }

    // emulate login baidu using java code
    public static void EmulateLoginBaiduUsingJava()
    {
        System.out.println("============ 程序说明 ============");
        System.out.println("功能：本程序是用来演示使用Java代码去实现模拟登陆百度");
        System.out.println("注意事项：部分百度账户，在登陆时会出现：");
        System.out.println("1.部分百度账户，在登陆时会出现：");
        System.out.println("系统检测到您的帐号疑似被盗，存在安全风险。请尽快修改密码。");
        System.out.println("此时，本程序，无法成功模拟登陆，请自行按照提示去修改密码后，就可以了。");

        boolean bLoginBaiduOk = false;
        List<Cookie> curCookieList;

        //step1: login baidu, got cookie BAIDUID
        System.out.println("====== 步骤1：获得BAIDUID的Cookie ======");
        String strTokenValue = "";
        boolean bGotCookieBaiduid = false;
        String strBaiduUrl = "http://www.baidu.com/";
        HttpResponse baiduResp = crl.getUrlResponse(strBaiduUrl);

        curCookieList =crl.getCurCookieStore().getCookies();
        crl.dbgPrintCookies(curCookieList, strBaiduUrl);
        for(Cookie ck : curCookieList)
        {
            String cookieName = ck.getName();
            if(cookieName.equals("BAIDUID"))
            {
                bGotCookieBaiduid = true;
            }
        }
        if (bGotCookieBaiduid)
        {
            System.out.println("正确：已找到cookie BAIDUID");
        }
        else
        {
            System.out.println("错误：没有找到cookie BAIDUID ！");
        }

        //step2: login, pass paras, extract resp cookie
        System.out.println("====== 步骤2：提取login_token ======");
        boolean bExtractTokenValueOK = false;
        if(bGotCookieBaiduid)
        {
            //https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true
            String getapiUrl = "https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true";
            String getApiRespHtml = crl.getUrlRespHtml(getapiUrl);

            curCookieList =crl.getCurCookieStore().getCookies();
            crl.dbgPrintCookies(curCookieList, getapiUrl);

            //bdPass.api.params.login_token='3cf421493884e0fe9080593d05f4744f';
            Pattern tokenValP = Pattern.compile("bdPass\\.api\\.params\\.login_token='(?<tokenVal>\\w+)';");
            Matcher tokenValMatcher = tokenValP.matcher(getApiRespHtml);
            //boolean foundTokenValue = tokenValMatcher.matches(); // will not match, but can search to find it
            boolean foundTokenValue = tokenValMatcher.find();
            if(foundTokenValue)
            {
                strTokenValue = tokenValMatcher.group("tokenVal"); //3cf421493884e0fe9080593d05f4744f
                System.out.println("正确：找到 bdPass.api.params.login_token=" + strTokenValue);

                bExtractTokenValueOK = true;
            }
            else
            {
                System.out.println("错误：没找到bdPass.api.params.login_token !");
            }
        }

        //step3: verify returned cookies
        if (bGotCookieBaiduid && bExtractTokenValueOK)
        {
            System.out.println("======步骤3：登陆百度并检验返回的Cookie ======");

            /*
            //Note:
            //here, has verify, not manually update some cookie's domain and expiry
            //also can emulate baidu successfully

            //do some workaround to makesure here cookie H_PS_PSSID not expire
            //[version: 0][name: H_PS_PSSID][value: 3359_3341_2776_1424_2981][domain: .baidu.com][path: /][expiry: null]

            //Thu Sep 17 14:22:08 CST 2043
            //Date newExpiryDate = new Date(2043, 9, 17);
            Date newExpiryDate = new Date(143, 9, 17);
            //Calendar newExpiryCalendar = new GregorianCalendar(2043, 9, 17, 14, 22, 8);

            BasicClientCookie hPsPssidCookie = null;
            BasicClientCookie dbsvrtmCookie = null;
            //int hPsPssidCookieIdx = 0;

            curCookieList = crl.getCurCookieList();
            for(Cookie ck : curCookieList)
            {
                if(ck.getName().equalsIgnoreCase("H_PS_PSSID"))
                {
                    //hPsPssidCookieIdx = curCookieList.indexOf(ck);
                    hPsPssidCookie = (BasicClientCookie) ck;
                    hPsPssidCookie.setExpiryDate(newExpiryDate);
                    ck = hPsPssidCookie;
                    //break;
                }

                if(ck.getName().equalsIgnoreCase("BDSVRTM"))
                {
                    dbsvrtmCookie = (BasicClientCookie) ck;
                    dbsvrtmCookie.setDomain(".baidu.com");
                    dbsvrtmCookie.setExpiryDate(newExpiryDate);
                    ck = dbsvrtmCookie;
                    //break;
                }
            }
            crl.setCurCookieList(curCookieList);
            */

            String staticPageUrl = "http://www.baidu.com/cache/user/html/jump.html";

            List<NameValuePair> postDict = new ArrayList<NameValuePair>();
            //ArrayList<NameValuePair> headerDict = new ArrayList<NameValuePair>();
            //postDict.add(new BasicNameValuePair("ppui_logintime", ""));
            postDict.add(new BasicNameValuePair("charset", "utf-8"));
            //postDict.add(new BasicNameValuePair("codestring", ""));
            postDict.add(new BasicNameValuePair("token", strTokenValue));
            postDict.add(new BasicNameValuePair("isPhone", "false"));
            postDict.add(new BasicNameValuePair("index", "0"));
            //postDict.add(new BasicNameValuePair("u", ""));
            //postDict.add(new BasicNameValuePair("safeflg", "0"));
            postDict.add(new BasicNameValuePair("staticpage", staticPageUrl));
            postDict.add(new BasicNameValuePair("loginType", "1"));
            postDict.add(new BasicNameValuePair("tpl", "mn"));
            postDict.add(new BasicNameValuePair("callback", "parent.bdPass.api.login._postCallback"));

            //get input baidu username and password
            String strBaiduUsername = "";
            String strBaiduPassword = "";
            Scanner inputReader = new Scanner(System.in);
            System.out.println("Please Enter Your:" );
            System.out.println("Baidu Username:" );
            strBaiduUsername = inputReader.nextLine();
            //System.out.println("You Entered Username=" + strBaiduUsername);
            System.out.println("Baidu Password:" );
            strBaiduPassword = inputReader.nextLine();
            //System.out.println("You Entered Password=" + strBaiduPassword);
            inputReader.close();

            postDict.add(new BasicNameValuePair("username", strBaiduUsername));
            postDict.add(new BasicNameValuePair("password", strBaiduPassword));

            postDict.add(new BasicNameValuePair("verifycode", ""));
            postDict.add(new BasicNameValuePair("mem_pass", "on"));

            String baiduMainLoginUrl = "https://passport.baidu.com/v2/api/?login";
            String loginBaiduRespHtml = crl.getUrlRespHtml(baiduMainLoginUrl, null, postDict);

            //Map cookieNameDict = new Map();
            //Map cookieNameDict = new Hashtable();
            HashMap<Object, Boolean> cookieNameDict = new HashMap<Object, Boolean>();
            cookieNameDict.put("BDUSS", false);
            cookieNameDict.put("PTOKEN", false);
            cookieNameDict.put("STOKEN", false);
            //Set-Cookie: SAVEUSERID=deleted; expires=Mon, 17-Sep-2012 09:45:03 GMT; path=/; domain=passport.baidu.com; httponly,
            //cookieNameDict.put("SAVEUSERID", false);

            curCookieList = crl.getCurCookieList();
            for(Object objCookieName : cookieNameDict.keySet().toArray())
            {
                String strCookieName = objCookieName.toString();
                for(Cookie ck: curCookieList)
                {
                    if(strCookieName.equalsIgnoreCase(ck.getName()))
                    {
                        cookieNameDict.put(strCookieName, true);
                    }
                }
            }

            boolean bAllCookiesFound = true;
            for (Object  objFoundCurCookie : cookieNameDict.values())
            {
                bAllCookiesFound = bAllCookiesFound && Boolean.parseBoolean(objFoundCurCookie.toString());
            }

            bLoginBaiduOk = bAllCookiesFound;

            if (bLoginBaiduOk)
            {
                System.out.println("成功模拟登陆百度首页！" );
            }
            else
            {
                System.out.println("模拟登陆百度首页 失败！");
                System.out.println("所返回的HTML源码为：" + loginBaiduRespHtml);
            }
        }

        return;
    }
}

成功模拟登陆百度，输出效果截图：

完整的Eclipse项目源码下载：

EmulateLoginBaidu_java_2013-09-17_crifanLibVersion.7z

注意事项

需要导入org.apache.http的库

详见：【已解决】Eclipse的java代码出错：The import org.apache cannot be resolved

其中用到的crifanLib的java代码详见：

crifanLib.java

部分百度账户，在登陆时会出现 系统检测到您的帐号疑似被盗，存在安全风险。请尽快修改密码。，此时本程序无法成功模拟登陆，请自行按照提示去修改密码后，即可成功模拟登录

具体过程详见：

【教程】模拟登陆百度之Java代码版

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 20:27:07

用PHP写爬虫

用PHP写爬虫

整理之前找的一点相关资料：

PHP的爬虫相关库或框架：

PHPCrawl webcrawler library/framework

PHP HTTP protocol client

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:59:20

附录

附录

下面列出相关参考资料。

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:54:01

名词解释

名词解释

页面爬取策略=爬虫策略

在爬虫系统中,等待抓取URL队列是很重要的组成部分,等待抓取URL队列中的URL的顺序排列方式也是一个很重要的问题,因为这会决定到先抓取哪个页面,后抓取哪个页面.而决定这些URL排列顺序的方法,叫做抓取策略.下面主要介绍几种常见的抓取策略：

深度优先遍历策略

深度优先遍历策略是指网络爬虫会从起始页开始,一个链接一个链接跟踪下去,直到处理完这条线路之后才会转入下一个起始页,继续跟踪链接.遍历的路径为：A-F-G ,E-H-I ,B ,C, D

宽度优先遍历策略

宽度优先遍历策略的基本思路就是,将新下载网页中发现的链接直接放入待抓取URL队列的末尾.也就是说网络爬虫会优先抓取起始网页中链接的所有网页,所有网页都抓取完之后,再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页.它的路径可以这样写:A-B-C-D-E-F ,G ,H, I

反向链接数策略

反向链接数是指一个网页被其他网页链接指向的数量,同时反向链接数也是表示一个网页的内容受到其他人的推荐的程度.因此,很多时候搜索引擎的抓取系统会使用这个指标来评价网页的重要程度,从而决定不同网页的抓取先后顺序.

而然在真实的网络环境中,由于许多广告链接、作弊链接等等的存在,反向链接数不能完全等同于重要程度.因此,许多的搜索引擎往往考虑一些可靠的反向链接数.

OPIC策略策略

这种算法实际上也是对网络页面进行一个重要性的打分.在算法开始前,会给所有页面一个相同的初始现金（cash）.当下载了某个页面P之后,将P的现金分摊给所有从P中分析出的链接,并且将P的现金清空.对于待抓取URL队列中的所有页面按照现金数进行排序

大站优先策略

对于待抓取URL队列中的所有网页,根据所属的网站进行分类.对于待下载页面数多的网站,优先下载.这个策略也因此叫做大站优先策略

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:54:14

参考资料

参考资料

【未解决】搞懂IP代理池相关概念和逻辑

crifanLib.cs之Http

HTTP知识总结

app抓包利器：Charles

JSON详解

主流数据格式：JSON

【已解决】C#中解析Json字符串

【记录】Python中尝试用lxml去解析html

Python爬虫框架：PySpider

Scrapy

Selenium知识总结

主流Python框架：Scrapy

【记录】C#中的HTML解析 – 在路上

如何用Python写爬虫

XPath知识总结

【整理】和PHP的HTTP,网页抓取,网络爬虫相关的库,框架,资料 – 在路上

【教程】抓取网并提取网页中所需要的信息 之 C#版

【教程】模拟登陆网站 之 C#版（内含两种版本的完整的可运行的代码）

【教程】模拟登陆网站 之 Python版（内含两种版本的完整的可运行的代码）

【记录】用go语言实现模拟登陆百度

【教程】模拟登陆百度之Java代码版

Python心得：操作CSV和Excel

主流关系数据库：MySQL

主流文档型数据库：MongoDB

Python中的正则表达式：re模块详解

【整理】Mac中用Charles抓包iOS或Android手机app中包括https的数据

【记录】模拟登陆google

【教程】如何抓取动态网页内容

【教程】以抓取网易博客帖子中的最近读者信息为例，手把手教你如何抓取动态网页中的内容

【经验总结】Http，网页访问，HttpRequest，HttpResponse相关的知识 – 在路上

如何用Python，C#等语言去实现抓取静态网页+抓取动态网页+模拟登陆网站 – 在路上

字符编码详解与应用

安卓应用的安全和破解

Grab

python-goose

PySpider

Portia

newspaper

ruia

Cola

Sasila

Nutch

Heritrix

crawler4j

WebMagic

Colly

Pholcus

headless-chrome-crawler

scrapy中的提取正文的方法-python,爬虫,scrapy研究-51CTO博客

基于Python的Scrapy爬虫入门：页面提取 SegmentFault

Scrapy定向爬虫教程(二)——提取网页内容 - 春华秋实 - CSDN博客

Scrapy笔记04- Selector详解 | 飞污熊

Scrapy爬虫抓取网站数据 | ShinChan's Blog

Scrapy爬虫入门教程十二 Link Extractors（链接提取器） - inke的博客 - CSDN博客

基于WebMagic的CSDN博客爬虫 - zhuqiuhui的专栏 - CSDN博客

Heritrix与Nutch对比 - 爱专集

Nutch、heritrix、crawler4j优缺点 - CSDN博客

爬虫用哪个好？ - 知乎

作为基础服务的数据采集，发展到哪个阶段了？搜狐科技搜狐网

Python3网络爬虫(四)：使用User Agent和代理IP隐藏身份 - Jack-Cui - CSDN博客

Python 爬虫一些常用的UA(user-agent) - abe_abd的博客 - CSDN博客

如何评价可以自动更换 User-Agent 的爬虫设计？ - 知乎

DarkSand/Sasila: 一个灵活、友好的爬虫框架

Python有哪些常见的、好用的爬虫框架？ - 知乎

8个最高效的Python爬虫框架，你用过几个？ - 个人文章 - SegmentFault 思否

爬虫的几种抓取策略 | 阿布云 - 因为专业·所以简单

【爬虫工程师招聘】智慧芽爬虫工程师招聘-BOSS直聘

【数据采集招聘】智慧芽数据采集招聘-BOSS直聘

【高级爬虫工程师招聘】智慧芽高级爬虫工程师招聘-BOSS直聘

如何对知乎内容进行爬虫？ - 知乎

用爬虫在各大机场自动签到获取流量

每天理财网站登陆签到获取积分

浦发信用卡自动签到

自制BILIBILI弹幕爬取，签到，抢楼等爬虫

Selenium

梦见蜘蛛网_国学易经

Python爬虫原理 - Python开发之路 - 博客园

蜻蜓代理 - 企业级高质量代理ip平台

讯代理-爬虫代理-HTTP代理-代理服务器

BruceDone/awesome-crawler: A collection of awesome web crawler,spider in different languages

crifan.org，使用署名4.0国际(CC BY 4.0)协议发布 all right reserved，powered by Gitbook最后更新： 2020-07-30 19:54:07