前面讲解了Scrapy中各个模块基本使用方法以及代理池、Cookies池。接下来咱们以一个反爬比较强的网站新浪微博为例,来实现一下Scrapy的大规模爬取。
html
本次爬取的目标是新浪微博用户的公开基本信息,如用户昵称、头像、用户的关注、粉丝列表以及发布的微博等,这些信息抓取以后保存至MongoDB。git
请确保前文所讲的代理池、Cookies池已经实现并能够正常运行,安装Scrapy、PyMongo库。
github
首先咱们要实现用户的大规模爬取。这里采用的爬取方式是,以微博的几个大V为起始点,爬取他们各自的粉丝和关注列表,而后获取粉丝和关注列表的粉丝和关注列表,以此类推,这样下去就能够实现递归爬取。若是一个用户与其余用户有社交网络上的关联,那他们的信息就会被爬虫抓取到,这样咱们就能够作到对全部用户的爬取。经过这种方式,咱们能够获得用户的惟一ID,再根据ID获取每一个用户发布的微博便可。
ajax
这里咱们选取的爬取站点是:https://m.weibo.cn,此站点是微博移动端的站点。打开该站点会跳转到登陆页面,这是由于主页作了登陆限制。不过咱们能够绕过登陆限制,直接打开某个用户详情页面,例如打开周冬雨的微博,连接为:https://m.weibo.cn/u/1916655407,便可进入其我的详情页面,以下图所示。
mongodb
咱们在页面最上方能够看到周冬雨的关注和粉丝数量。咱们点击关注,进入到她的关注列表,以下图所示。数据库
咱们打开开发者工具,切换到XHR过滤器,一直下拉关注列表,便可看到下方会出现不少Ajax请求,这些请求就是获取周冬雨的关注列表的Ajax请求,以下图所示。json
咱们打开第一个Ajax请求,它的连接为:https://m.weibo.cn/api/container/getIndex?containerid=231051api
请求类型是GET类型,返回结果是JSON格式,咱们将其展开以后便可看到其关注的用户的基本信息。接下来咱们只须要构造这个请求的参数。此连接一共有7个参数,以下图所示。bash
其中最主要的参数就是containerid
和page
。有了这两个参数,咱们一样能够获取请求结果。咱们能够将接口精简为:https://m.weibo.cn/api/container/getIndex?containerid=231051服务器
container_id
的前半部分是固定的,后半部分是用户的id。因此这里参数就能够构造出来了,只须要修改
container_id
最后的
id
和
page
参数便可获取分页形式的关注列表信息。
利用一样的方法,咱们也能够分析用户详情的Ajax连接、用户微博列表的Ajax连接,以下所示:
# 用户详情API user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}' # 关注列表API follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}' # 粉丝列表API fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}' # 微博列表API weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}'复制代码
此处的uid
和page
分别表明用户ID和分页页码。
注意,这个API可能随着时间的变化或者微博的改版而变化,以实测为准。
咱们从几个大V开始抓取,抓取他们的粉丝、关注列表、微博信息,而后递归抓取他们的粉丝和关注列表的粉丝、关注列表、微博信息,递归抓取,最后保存微博用户的基本信息、关注和粉丝列表、发布的微博。
咱们选择MongoDB做存储的数据库,能够更方便地存储用户的粉丝和关注列表。
接下来咱们用Scrapy来实现这个抓取过程。首先建立一个项目,命令以下所示:
scrapy startproject weibo复制代码
进入项目中,新建一个Spider,名为weibocn,命令以下所示:
scrapy genspider weibocn m.weibo.cn复制代码
咱们首先修改Spider,配置各个Ajax的URL,选取几个大V,将他们的ID赋值成一个列表,实现start_requests()
方法,也就是依次抓取各个大V的我的详情,而后用parse_user()
进行解析,以下所示:
from scrapy import Request, Spider class WeiboSpider(Spider): name = 'weibocn' allowed_domains = ['m.weibo.cn'] user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}' follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}' fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}' weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}' start_users = ['3217179555', '1742566624', '2282991915', '1288739185', '3952070245', '5878659096'] def start_requests(self): for uid in self.start_users: yield Request(self.user_url.format(uid=uid), callback=self.parse_user) def parse_user(self, response): self.logger.debug(response)复制代码
接下来咱们解析用户的基本信息并生成Item。这里咱们先定义几个Item,如用户、用户关系、微博的Item,以下所示:
from scrapy import Item, Field class UserItem(Item): collection = 'users' id = Field() name = Field() avatar = Field() cover = Field() gender = Field() description = Field() fans_count = Field() follows_count = Field() weibos_count = Field() verified = Field() verified_reason = Field() verified_type = Field() follows = Field() fans = Field() crawled_at = Field() class UserRelationItem(Item): collection = 'users' id = Field() follows = Field() fans = Field() class WeiboItem(Item): collection = 'weibos' id = Field() attitudes_count = Field() comments_count = Field() reposts_count = Field() picture = Field() pictures = Field() source = Field() text = Field() raw_text = Field() thumbnail = Field() user = Field() created_at = Field() crawled_at = Field()复制代码
这里定义了collection
字段,指明保存的Collection的名称。用户的关注和粉丝列表直接定义为一个单独的UserRelationItem
,其中id
就是用户的ID,follows
就是用户关注列表,fans
是粉丝列表,但这并不意味着咱们会将关注和粉丝列表存到一个单独的Collection里。后面咱们会用Pipeline对各个Item进行处理、合并存储到用户的Collection里,所以Item和Collection并不必定是彻底对应的。
咱们开始解析用户的基本信息,实现parse_user()
方法,以下所示:
def parse_user(self, response): """ 解析用户信息 :param response: Response对象 """ result = json.loads(response.text) if result.get('data').get('userInfo'): user_info = result.get('data').get('userInfo') user_item = UserItem() field_map = { 'id': 'id', 'name': 'screen_name', 'avatar': 'profile_image_url', 'cover': 'cover_image_phone', 'gender': 'gender', 'description': 'description', 'fans_count': 'followers_count', 'follows_count': 'follow_count', 'weibos_count': 'statuses_count', 'verified': 'verified', 'verified_reason': 'verified_reason', 'verified_type': 'verified_type' } for field, attr in field_map.items(): user_item[field] = user_info.get(attr) yield user_item # 关注 uid = user_info.get('id') yield Request(self.follow_url.format(uid=uid, page=1), callback=self.parse_follows, meta={'page': 1, 'uid': uid}) # 粉丝 yield Request(self.fan_url.format(uid=uid, page=1), callback=self.parse_fans, meta={'page': 1, 'uid': uid}) # 微博 yield Request(self.weibo_url.format(uid=uid, page=1), callback=self.parse_weibos, meta={'page': 1, 'uid': uid})复制代码
在这里咱们一共完成了两个操做。
解析JSON提取用户信息并生成UserItem返回。咱们并无采用常规的逐个赋值的方法,而是定义了一个字段映射关系。咱们定义的字段名称可能和JSON中用户的字段名称不一样,因此在这里定义成一个字典,而后遍历字典的每一个字段实现逐个字段的赋值。
构造用户的关注、粉丝、微博的第一页的连接,并生成Request,这里须要的参数只有用户的ID。另外,初始分页页码直接设置为1便可。
接下来,咱们还须要保存用户的关注和粉丝列表。以关注列表为例,其解析方法为parse_follows()
,实现以下所示:
def parse_follows(self, response): """ 解析用户关注 :param response: Response对象 """ result = json.loads(response.text) if result.get('ok') and result.get('data').get('cards') and len(result.get('data').get('cards')) and result.get('data').get('cards')[-1].get( 'card_group'): # 解析用户 follows = result.get('data').get('cards')[-1].get('card_group') for follow in follows: if follow.get('user'): uid = follow.get('user').get('id') yield Request(self.user_url.format(uid=uid), callback=self.parse_user) # 关注列表 uid = response.meta.get('uid') user_relation_item = UserRelationItem() follows = [{'id': follow.get('user').get('id'), 'name': follow.get('user').get('screen_name')} for follow in follows] user_relation_item['id'] = uid user_relation_item['follows'] = follows user_relation_item['fans'] = [] yield user_relation_item # 下一页关注 page = response.meta.get('page') + 1 yield Request(self.follow_url.format(uid=uid, page=page), callback=self.parse_follows, meta={'page': page, 'uid': uid})复制代码
那么在这个方法里面咱们作了以下三件事。
解析关注列表中的每一个用户信息并发起新的解析请求。咱们首先解析关注列表的信息,获得用户的ID,而后再利用user_url
构造访问用户详情的Request,回调就是刚才所定义的parse_user()
方法。
提取用户关注列表内的关键信息并生成UserRelationItem
。id
字段直接设置成用户的ID,JSON返回数据中的用户信息有不少冗余字段。在这里咱们只提取了关注用户的ID和用户名,而后把它们赋值给follows
字段,fans
字段设置成空列表。这样咱们就创建了一个存有用户ID和用户部分关注列表的UserRelationItem
,以后合而且保存具备同一个ID的UserRelationItem
的关注和粉丝列表。
提取下一页关注。只须要将此请求的分页页码加1便可。分页页码经过Request的meta
属性进行传递,Response的meta
来接收。这样咱们构造并返回下一页的关注列表的Request。
抓取粉丝列表的原理和抓取关注列表原理相同,在此再也不赘述。
接下来咱们还差一个方法的实现,即parse_weibos()
,它用来抓取用户的微博信息,实现以下所示:
def parse_weibos(self, response): """ 解析微博列表 :param response: Response对象 """ result = json.loads(response.text) if result.get('ok') and result.get('data').get('cards'): weibos = result.get('data').get('cards') for weibo in weibos: mblog = weibo.get('mblog') if mblog: weibo_item = WeiboItem() field_map = { 'id': 'id', 'attitudes_count': 'attitudes_count', 'comments_count': 'comments_count', 'created_at': 'created_at', 'reposts_count': 'reposts_count', 'picture': 'original_pic', 'pictures': 'pics', 'source': 'source', 'text': 'text', 'raw_text': 'raw_text', 'thumbnail': 'thumbnail_pic' } for field, attr in field_map.items(): weibo_item[field] = mblog.get(attr) weibo_item['user'] = response.meta.get('uid') yield weibo_item # 下一页微博 uid = response.meta.get('uid') page = response.meta.get('page') + 1 yield Request(self.weibo_url.format(uid=uid, page=page), callback=self.parse_weibos, meta={'uid': uid, 'page': page})复制代码
在这里parse_weibos()
方法完成了两件事。
提取用户的微博信息,并生成WeiboItem。这里一样创建了一个字段映射表,实现批量字段赋值。
提取下一页的微博列表。这里一样须要传入用户ID和分页页码。
目前为止,微博的Spider已经完成。后面还须要对数据进行数据清洗存储,以及对接代理池、Cookies池来防止反爬虫。
有些微博的时间可能不是标准的时间,好比它可能显示为刚刚、几分钟前、几小时前、昨天等。这里咱们须要统一转化这些时间,实现一个parse_time()
方法,以下所示:
def parse_time(self, date): if re.match('刚刚', date): date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time())) if re.match('\d+分钟前', date): minute = re.match('(\d+)', date).group(1) date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() - float(minute) * 60)) if re.match('\d+小时前', date): hour = re.match('(\d+)', date).group(1) date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() - float(hour) * 60 * 60)) if re.match('昨天.*', date): date = re.match('昨天(.*)', date).group(1).strip() date = time.strftime('%Y-%m-%d', time.localtime() - 24 * 60 * 60) + ' ' + date if re.match('\d{2}-\d{2}', date): date = time.strftime('%Y-', time.localtime()) + date + ' 00:00' return date复制代码
咱们用正则来提取一些关键数字,用time库来实现标准时间的转换。
以X分钟前的处理为例,爬取的时间会赋值为created_at
字段。咱们首先用正则匹配这个时间,表达式写做\d+分钟前
,若是提取到的时间符合这个表达式,那么就提取出其中的数字,这样就能够获取分钟数了。接下来使用time
模块的strftime()
方法,第一个参数传入要转换的时间格式,第二个参数就是时间戳。在这里咱们用当前的时间戳减去此分钟数乘以60就是当时的时间戳,这样咱们就能够获得格式化后的正确时间了。
而后Pipeline能够实现以下处理:
class WeiboPipeline(): def process_item(self, item, spider): if isinstance(item, WeiboItem): if item.get('created_at'): item['created_at'] = item['created_at'].strip() item['created_at'] = self.parse_time(item.get('created_at'))复制代码
咱们在Spider里没有对crawled_at
字段赋值,它表明爬取时间,咱们能够统一将其赋值为当前时间,实现以下所示:
class TimePipeline(): def process_item(self, item, spider): if isinstance(item, UserItem) or isinstance(item, WeiboItem): now = time.strftime('%Y-%m-%d %H:%M', time.localtime()) item['crawled_at'] = now return item复制代码
在这里咱们判断了Item若是是UserItem或WeiboItem类型,那么就给它的crawled_at
字段赋值为当前时间。
经过上面的两个Pipeline,咱们便完成了数据清洗工做,这里主要是时间的转换。
数据清洗完毕以后,咱们就要将数据保存到MongoDB数据库。咱们在这里实现MongoPipeline类,以下所示:
import pymongo class MongoPipeline(object): def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] self.db[UserItem.collection].create_index([('id', pymongo.ASCENDING)]) self.db[WeiboItem.collection].create_index([('id', pymongo.ASCENDING)]) def close_spider(self, spider): self.client.close() def process_item(self, item, spider): if isinstance(item, UserItem) or isinstance(item, WeiboItem): self.db[item.collection].update({'id': item.get('id')}, {'$set': item}, True) if isinstance(item, UserRelationItem): self.db[item.collection].update( {'id': item.get('id')}, {'$addToSet': { 'follows': {'$each': item['follows']}, 'fans': {'$each': item['fans']} } }, True) return item复制代码
当前的MongoPipeline和前面咱们所写的有所不一样,主要有如下几点。
open_spider()
方法里添加了Collection的索引,在这里为两个Item都添加了索引,索引的字段是id
。因为咱们此次是大规模爬取,爬取过程涉及数据的更新问题,因此咱们为每一个Collection创建了索引,这样能够大大提升检索效率。
在process_item()
方法里存储使用的是update()
方法,第一个参数是查询条件,第二个参数是爬取的Item。这里咱们使用了$set
操做符,若是爬取到重复的数据便可对数据进行更新,同时不会删除已存在的字段。若是这里不加$set
操做符,那么会直接进行item
替换,这样可能会致使已存在的字段如关注和粉丝列表清空。第三个参数设置为True,若是数据不存在,则插入数据。这样咱们就能够作到数据存在即更新、数据不存在即插入,从而得到去重的效果。
对于用户的关注和粉丝列表,咱们使用了一个新的操做符,叫做$addToSet
,这个操做符能够向列表类型的字段插入数据同时去重。它的值就是须要操做的字段名称。这里利用了$each
操做符对须要插入的列表数据进行了遍历,以逐条插入用户的关注或粉丝数据到指定的字段。关于该操做更多解释能够参考MongoDB的官方文档,连接为:https://docs.mongodb.com/manual/reference/operator/update/addToSet/。
新浪微博的反爬能力很是强,咱们须要作一些防范反爬虫的措施才能够顺利完成数据爬取。
若是没有登陆而直接请求微博的API接口,这很是容易致使403状态码。这个状况咱们在Cookies池一节也提过。因此在这里咱们实现一个Middleware,为每一个Request添加随机的Cookies。
咱们先开启Cookies池,使API模块正常运行。例如在本地运行5000端口,访问:http://localhost:5000/weibo/random,便可获取随机的Cookies。固然也能够将Cookies池部署到远程的服务器,这样只须要更改访问的连接。
咱们在本地启动Cookies池,实现一个Middleware,以下所示:
class CookiesMiddleware(): def __init__(self, cookies_url): self.logger = logging.getLogger(__name__) self.cookies_url = cookies_url def get_random_cookies(self): try: response = requests.get(self.cookies_url) if response.status_code == 200: cookies = json.loads(response.text) return cookies except requests.ConnectionError: return False def process_request(self, request, spider): self.logger.debug('正在获取Cookies') cookies = self.get_random_cookies() if cookies: request.cookies = cookies self.logger.debug('使用Cookies ' + json.dumps(cookies)) @classmethod def from_crawler(cls, crawler): settings = crawler.settings return cls( cookies_url=settings.get('COOKIES_URL') )复制代码
咱们首先利用from_crawler()
方法获取了COOKIES_URL
变量,它定义在settings.py里,这就是刚才咱们所说的接口。接下来实现get_random_cookies()
方法,这个方法主要就是请求此Cookies池接口并获取接口返回的随机Cookies。若是成功获取,则返回Cookies;不然返回False
。
接下来,在process_request()
方法里,咱们给request
对象的cookies
属性赋值,其值就是获取的随机Cookies,这样咱们就成功地为每一次请求赋值Cookies了。
若是启用了该Middleware,每一个请求都会被赋值随机的Cookies。这样咱们就能够模拟登陆以后的请求,403状态码基本就不会出现。
微博还有一个反爬措施就是,检测到同一IP请求量过大时就会出现414状态码。若是遇到这样的状况能够切换代理。例如,在本地5555端口运行,获取随机可用代理的地址为:http://localhost:5555/random,访问这个接口便可获取一个随机可用代理。接下来咱们再实现一个Middleware,代码以下所示:
class ProxyMiddleware(): def __init__(self, proxy_url): self.logger = logging.getLogger(__name__) self.proxy_url = proxy_url def get_random_proxy(self): try: response = requests.get(self.proxy_url) if response.status_code == 200: proxy = response.text return proxy except requests.ConnectionError: return False def process_request(self, request, spider): if request.meta.get('retry_times'): proxy = self.get_random_proxy() if proxy: uri = 'https://{proxy}'.format(proxy=proxy) self.logger.debug('使用代理 ' + proxy) request.meta['proxy'] = uri @classmethod def from_crawler(cls, crawler): settings = crawler.settings return cls( proxy_url=settings.get('PROXY_URL') )复制代码
一样的原理,咱们实现了一个get_random_proxy()
方法用于请求代理池的接口获取随机代理。若是获取成功,则返回改代理,不然返回False
。在process_request()
方法中,咱们给request
对象的meta
属性赋值一个proxy
字段,该字段的值就是代理。
另外,赋值代理的判断条件是当前retry_times
不为空,也就是说第一次请求失败以后才启用代理,由于使用代理后访问速度会慢一些。因此咱们在这里设置了只有重试的时候才启用代理,不然直接请求。这样就能够保证在没有被封禁的状况下直接爬取,保证了爬取速度。
接下来,咱们在配置文件中启用这两个Middleware,修改settings.py以下所示:
DOWNLOADER_MIDDLEWARES = { 'weibo.middlewares.CookiesMiddleware': 554, 'weibo.middlewares.ProxyMiddleware': 555, }复制代码
注意这里的优先级设置,前文提到了Scrapy的默认Downloader Middleware的设置以下:
{ 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, }复制代码
要使得咱们自定义的CookiesMiddleware生效,它在内置的CookiesMiddleware以前调用。内置的CookiesMiddleware的优先级为700,因此这里咱们设置一个比700小的数字便可。
要使得咱们自定义的ProxyMiddleware生效,它在内置的HttpProxyMiddleware以前调用。内置的HttpProxyMiddleware的优先级为750,因此这里咱们设置一个比750小的数字便可。
到此为止,整个微博爬虫就实现完毕了。咱们运行以下命令启动爬虫:
scrapy crawl weibocn复制代码
输出结果以下所示:
2017-07-11 17:27:34 [urllib3.connectionpool] DEBUG: http://localhost:5000 "GET /weibo/random HTTP/1.1" 200 339 2017-07-11 17:27:34 [weibo.middlewares] DEBUG: 使用Cookies {"SCF": "AhzwTr_DxIGjgri_dt46_DoPzUqq-PSupu545JdozdHYJ7HyEb4pD3pe05VpbIpVyY1ciKRRWwUgojiO3jYwlBE.", "_T_WM": "8fe0bc1dad068d09b888d8177f1c1218", "SSOLoginState": "1501496388", "M_WEIBOCN_PARAMS": "uicode%3D20000174", "SUHB": "0tKqV4asxqYl4J", "SUB": "_2A250e3QUDeRhGeBM6VYX8y7NwjiIHXVXhBxcrDV6PUJbkdBeLXjckW2fUT8MWloekO4FCWVlIYJGJdGLnA.."} 2017-07-11 17:27:34 [weibocn] DEBUG: <200 https://m.weibo.cn/api/container/getIndex?uid=1742566624&type=uid&value=1742566624&containerid=1005051742566624> 2017-07-11 17:27:34 [scrapy.core.scraper] DEBUG: Scraped from <200 https://m.weibo.cn/api/container/getIndex?uid=1742566624&type=uid&value=1742566624&containerid=1005051742566624> {'avatar': 'https://tva4.sinaimg.cn/crop.0.0.180.180.180/67dd74e0jw1e8qgp5bmzyj2050050aa8.jpg', 'cover': 'https://tva3.sinaimg.cn/crop.0.0.640.640.640/6ce2240djw1e9oaqhwllzj20hs0hsdir.jpg', 'crawled_at': '2017-07-11 17:27', 'description': '成长,就是一个不断以为之前的本身是个傻逼的过程', 'fans_count': 19202906, 'follows_count': 1599, 'gender': 'm', 'id': 1742566624, 'name': '思想聚焦', 'verified': True, 'verified_reason': '微博知名博主,校导网编辑', 'verified_type': 0, 'weibos_count': 58393}复制代码
运行一段时间后,咱们即可以到MongoDB数据库查看数据,爬取下来的数据以下图所示。
针对用户信息,咱们不只爬取了其基本信息,还把关注和粉丝列表加到了follows
和fans
字段并作了去重操做。针对微博信息,咱们成功进行了时间转换处理,同时还保存了微博的图片列表信息。
本节代码地址为:https://github.com/Python3WebSpider/Weibo。
本节实现了新浪微博的用户及其粉丝关注列表和微博信息的爬取,还对接了Cookies池和代理池来处理反爬虫。不过如今是针对单机的爬取,后面咱们会将此项目修改成分布式爬虫,以进一步提升抓取效率。
本资源首发于崔庆才的我的博客静觅: Python3网络爬虫开发实战教程 | 静觅
如想了解更多爬虫资讯,请关注个人我的微信公众号:进击的Coder
weixin.qq.com/r/5zsjOyvEZ… (二维码自动识别)