]> git.lizzy.rs Git - nhentai.git/blob - nhentai/parser.py
e5b8cd3290f8b125e7f547cfca44e96d3dbd7f35
[nhentai.git] / nhentai / parser.py
1 # coding: utf-8
2 from __future__ import unicode_literals, print_function
3
4 import os
5 import re
6 import threadpool
7 import requests
8 import time
9 from bs4 import BeautifulSoup
10 from tabulate import tabulate
11
12 import nhentai.constant as constant
13 from nhentai.logger import logger
14
15
16 def request(method, url, **kwargs):
17     if not hasattr(requests, method):
18         raise AttributeError('\'requests\' object has no attribute \'{0}\''.format(method))
19
20     return requests.__dict__[method](url, proxies=constant.PROXY, verify=False, **kwargs)
21
22
23 def login_parser(username, password):
24     s = requests.Session()
25     s.proxies = constant.PROXY
26     s.verify = False
27     s.headers.update({'Referer': constant.LOGIN_URL})
28
29     s.get(constant.LOGIN_URL)
30     content = s.get(constant.LOGIN_URL).content
31     html = BeautifulSoup(content, 'html.parser')
32     csrf_token_elem = html.find('input', attrs={'name': 'csrfmiddlewaretoken'})
33
34     if not csrf_token_elem:
35         raise Exception('Cannot find csrf token to login')
36     csrf_token = csrf_token_elem.attrs['value']
37
38     login_dict = {
39         'csrfmiddlewaretoken': csrf_token,
40         'username_or_email': username,
41         'password': password,
42     }
43     resp = s.post(constant.LOGIN_URL, data=login_dict)
44     if 'Invalid username/email or password' in resp.text:
45         logger.error('Login failed, please check your username and password')
46         exit(1)
47
48     html = BeautifulSoup(s.get(constant.FAV_URL).content, 'html.parser')
49     count = html.find('span', attrs={'class': 'count'})
50     if not count:
51         logger.error("Can't get your number of favorited doujins. Did the login failed?")
52
53     count = int(count.text.strip('(').strip(')').replace(',', ''))
54     if count == 0:
55         logger.warning('No favorites found')
56         return []
57     pages = int(count / 25)
58
59     if pages:
60         pages += 1 if count % (25 * pages) else 0
61     else:
62         pages = 1
63
64     logger.info('You have %d favorites in %d pages.' % (count, pages))
65
66     if os.getenv('DEBUG'):
67         pages = 1
68
69     ret = []
70     doujinshi_id = re.compile('data-id="([\d]+)"')
71
72     def _callback(request, result):
73         ret.append(result)
74
75     thread_pool = threadpool.ThreadPool(5)
76
77     for page in range(1, pages + 1):
78         try:
79             logger.info('Getting doujinshi ids of page %d' % page)
80             resp = s.get(constant.FAV_URL + '?page=%d' % page).text
81             ids = doujinshi_id.findall(resp)
82             requests_ = threadpool.makeRequests(doujinshi_parser, ids, _callback)
83             [thread_pool.putRequest(req) for req in requests_]
84             thread_pool.wait()
85         except Exception as e:
86             logger.error('Error: %s, continue', str(e))
87
88     return ret
89
90
91 def doujinshi_parser(id_):
92     if not isinstance(id_, (int,)) and (isinstance(id_, (str,)) and not id_.isdigit()):
93         raise Exception('Doujinshi id({0}) is not valid'.format(id_))
94
95     id_ = int(id_)
96     logger.log(15, 'Fetching doujinshi information of id {0}'.format(id_))
97     doujinshi = dict()
98     doujinshi['id'] = id_
99     url = '{0}/{1}/'.format(constant.DETAIL_URL, id_)
100
101     try:
102         response = request('get', url).content
103     except Exception as e:
104         logger.critical(str(e))
105         raise SystemExit
106
107     html = BeautifulSoup(response, 'html.parser')
108     doujinshi_info = html.find('div', attrs={'id': 'info'})
109
110     title = doujinshi_info.find('h1').text
111     subtitle = doujinshi_info.find('h2')
112
113     doujinshi['name'] = title
114     doujinshi['subtitle'] = subtitle.text if subtitle else ''
115
116     doujinshi_cover = html.find('div', attrs={'id': 'cover'})
117     img_id = re.search('/galleries/([\d]+)/cover\.(jpg|png)$', doujinshi_cover.a.img.attrs['data-src'])
118     if not img_id:
119         logger.critical('Tried yo get image id failed')
120         exit(1)
121
122     doujinshi['img_id'] = img_id.group(1)
123     doujinshi['ext'] = img_id.group(2)
124
125     pages = 0
126     for _ in doujinshi_info.find_all('div', class_=''):
127         pages = re.search('([\d]+) pages', _.text)
128         if pages:
129             pages = pages.group(1)
130             break
131     doujinshi['pages'] = int(pages)
132
133     # gain information of the doujinshi
134     information_fields = doujinshi_info.find_all('div', attrs={'class': 'field-name'})
135     needed_fields = ['Characters', 'Artists', 'Language', 'Tags']
136     for field in information_fields:
137         field_name = field.contents[0].strip().strip(':')
138         if field_name in needed_fields:
139             data = [sub_field.contents[0].strip() for sub_field in
140                     field.find_all('a', attrs={'class': 'tag'})]
141             doujinshi[field_name.lower()] = ', '.join(data)
142
143     return doujinshi
144
145
146 def search_parser(keyword, page):
147     logger.debug('Searching doujinshis of keyword {0}'.format(keyword))
148     result = []
149     try:
150         response = request('get', url=constant.SEARCH_URL, params={'q': keyword, 'page': page}).content
151     except requests.ConnectionError as e:
152         logger.critical(e)
153         logger.warn('If you are in China, please configure the proxy to fu*k GFW.')
154         raise SystemExit
155
156     html = BeautifulSoup(response, 'html.parser')
157     doujinshi_search_result = html.find_all('div', attrs={'class': 'gallery'})
158     for doujinshi in doujinshi_search_result:
159         doujinshi_container = doujinshi.find('div', attrs={'class': 'caption'})
160         title = doujinshi_container.text.strip()
161         title = title if len(title) < 85 else title[:82] + '...'
162         id_ = re.search('/g/(\d+)/', doujinshi.a['href']).group(1)
163         result.append({'id': id_, 'title': title})
164     if not result:
165         logger.warn('Not found anything of keyword {}'.format(keyword))
166
167     return result
168
169
170 def __api_suspended_doujinshi_parser(id_):
171     if not isinstance(id_, (int,)) and (isinstance(id_, (str,)) and not id_.isdigit()):
172         raise Exception('Doujinshi id({0}) is not valid'.format(id_))
173
174     id_ = int(id_)
175     logger.log(15, 'Fetching information of doujinshi id {0}'.format(id_))
176     doujinshi = dict()
177     doujinshi['id'] = id_
178     url = '{0}/{1}'.format(constant.DETAIL_URL, id_)
179     i = 0
180     while 5 > i:
181         try:
182             response = request('get', url).json()
183         except Exception as e:
184             i += 1
185             if not i < 5:
186                 logger.critical(str(e))
187                 exit(1)
188             continue
189         break
190
191     doujinshi['name'] = response['title']['english']
192     doujinshi['subtitle'] = response['title']['japanese']
193     doujinshi['img_id'] = response['media_id']
194     doujinshi['ext'] = ''.join(map(lambda s: s['t'], response['images']['pages']))
195     doujinshi['pages'] = len(response['images']['pages'])
196
197     # gain information of the doujinshi
198     needed_fields = ['character', 'artist', 'language', 'tag']
199     for tag in response['tags']:
200         tag_type = tag['type']
201         if tag_type in needed_fields:
202             if tag_type == 'tag':
203                 if tag_type not in doujinshi:
204                     doujinshi[tag_type] = {}
205
206                 tag['name'] = tag['name'].replace(' ', '-')
207                 tag['name'] = tag['name'].lower()
208                 doujinshi[tag_type][tag['name']] = tag['id']
209             elif tag_type not in doujinshi:
210                 doujinshi[tag_type] = tag['name']
211             else:
212                 doujinshi[tag_type] += ', ' + tag['name']
213
214     return doujinshi
215
216
217 def __api_suspended_search_parser(keyword, page):
218     logger.debug('Searching doujinshis using keywords {0}'.format(keyword))
219     result = []
220     i = 0
221     while i < 5:
222         try:
223             response = request('get', url=constant.SEARCH_URL, params={'query': keyword, 'page': page}).json()
224         except Exception as e:
225             i += 1
226             if not i < 5:
227                 logger.critical(str(e))
228                 logger.warn('If you are in China, please configure the proxy to fu*k GFW.')
229                 exit(1)
230             continue
231         break
232
233     if 'result' not in response:
234         raise Exception('No result in response')
235
236     for row in response['result']:
237         title = row['title']['english']
238         title = title[:85] + '..' if len(title) > 85 else title
239         result.append({'id': row['id'], 'title': title})
240
241     if not result:
242         logger.warn('No results for keywords {}'.format(keyword))
243
244     return result
245
246
247 def print_doujinshi(doujinshi_list):
248     if not doujinshi_list:
249         return
250     doujinshi_list = [(i['id'], i['title']) for i in doujinshi_list]
251     headers = ['id', 'doujinshi']
252     logger.info('Search Result\n' +
253                 tabulate(tabular_data=doujinshi_list, headers=headers, tablefmt='rst'))
254
255
256 def __api_suspended_tag_parser(tag_id, max_page=1):
257     logger.info('Searching for doujinshi with tag id {0}'.format(tag_id))
258     result = []
259     response = request('get', url=constant.TAG_API_URL, params={'sort': 'popular', 'tag_id': tag_id}).json()
260     page = max_page if max_page <= response['num_pages'] else int(response['num_pages'])
261
262     for i in range(1, page + 1):
263         logger.info('Getting page {} ...'.format(i))
264
265         if page != 1:
266             response = request('get', url=constant.TAG_API_URL,
267                                params={'sort': 'popular', 'tag_id': tag_id}).json()
268     for row in response['result']:
269         title = row['title']['english']
270         title = title[:85] + '..' if len(title) > 85 else title
271         result.append({'id': row['id'], 'title': title})
272
273     if not result:
274         logger.warn('No results for tag id {}'.format(tag_id))
275
276     return result
277
278
279 def tag_parser(tag_name, max_page=1):
280     result = []
281     tag_name = tag_name.lower()
282     tag_name = tag_name.replace(' ', '-')
283
284     for p in range(1, max_page + 1):
285         logger.debug('Fetching page {0} for doujinshi with tag \'{1}\''.format(p, tag_name))
286         response = request('get', url='%s/%s?page=%d' % (constant.TAG_URL, tag_name, p)).content
287
288         html = BeautifulSoup(response, 'html.parser')
289         doujinshi_items = html.find_all('div', attrs={'class': 'gallery'})
290         if not doujinshi_items:
291             logger.error('Cannot find doujinshi id of tag \'{0}\''.format(tag_name))
292             return
293
294         for i in doujinshi_items:
295             doujinshi_id = i.a.attrs['href'].strip('/g')
296             doujinshi_title = i.a.text.strip()
297             doujinshi_title = doujinshi_title if len(doujinshi_title) < 85 else doujinshi_title[:82] + '...'
298             result.append({'title': doujinshi_title, 'id': doujinshi_id})
299
300     if not result:
301         logger.warn('No results for tag \'{}\''.format(tag_name))
302
303     return result
304
305
306 if __name__ == '__main__':
307     print(doujinshi_parser("32271"))