mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-17 06:02:18 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a6ba6485e | ||
|
|
177a299ced | ||
|
|
4a1fe035af | ||
|
|
db86f81be7 | ||
|
|
020d412342 | ||
|
|
6f57197afb | ||
|
|
57393f619d | ||
|
|
d5dd8cec00 | ||
|
|
3652e009d9 | ||
|
|
ecde92d05e | ||
|
|
a8d3851956 | ||
|
|
c71873231d | ||
|
|
3823b1e593 | ||
|
|
6c779e8d4b | ||
|
|
c24015a93d | ||
|
|
2c56e6994a | ||
|
|
6791e9a2f8 | ||
|
|
64652f3994 | ||
|
|
4f5b223b60 | ||
|
|
2a3711687f | ||
|
|
612842f255 | ||
|
|
fe9cd653e7 | ||
|
|
82e7b19d6d | ||
|
|
5d7c36399f | ||
|
|
f0e66ef465 |
@@ -14,7 +14,7 @@ A clear and concise description of what you want to happen.
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**If the feature request is accepted, would you be willing to submit a PR?**
|
||||
**If the feature request is accepted, would you be willing to submit a pull request?**
|
||||
Yes / No
|
||||
<!-- Help can be provided if you need assistance submitting a PR -->
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ verify_ssl = true
|
||||
[dev-packages]
|
||||
pylint = "*"
|
||||
mypy = "*"
|
||||
sphinx = "*"
|
||||
sphinx = "~=2.3"
|
||||
pyinstaller = "*"
|
||||
pefile = "*"
|
||||
pywin32-ctypes = "*"
|
||||
|
||||
Generated
+108
-100
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "33408a60fda1a56bf680462fad21643ae0159bc13f3b85d8f10ebf3eef7d8725"
|
||||
"sha256": "58caf1be034bed9aa7ebb8c78e75ffa7d7abe820d014b6398c444a5db566b306"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -16,10 +16,10 @@
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
],
|
||||
"version": "==2019.11.28"
|
||||
"version": "==2020.6.20"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -30,25 +30,25 @@
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"version": "==2.8"
|
||||
"version": "==2.10"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.22.0"
|
||||
"version": "==2.24.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
|
||||
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
|
||||
],
|
||||
"version": "==1.25.8"
|
||||
"version": "==1.25.10"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
@@ -68,10 +68,10 @@
|
||||
},
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
|
||||
"sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
|
||||
"sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
|
||||
"sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
|
||||
],
|
||||
"version": "==2.3.3"
|
||||
"version": "==2.4.2"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
@@ -82,10 +82,10 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
],
|
||||
"version": "==2019.11.28"
|
||||
"version": "==2020.6.20"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -109,10 +109,10 @@
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"version": "==2.8"
|
||||
"version": "==2.10"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
@@ -130,10 +130,10 @@
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
|
||||
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
|
||||
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
|
||||
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
|
||||
],
|
||||
"version": "==2.11.1"
|
||||
"version": "==2.11.2"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
"hashes": [
|
||||
@@ -208,23 +208,23 @@
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a",
|
||||
"sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7",
|
||||
"sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2",
|
||||
"sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474",
|
||||
"sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0",
|
||||
"sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217",
|
||||
"sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749",
|
||||
"sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6",
|
||||
"sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf",
|
||||
"sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36",
|
||||
"sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b",
|
||||
"sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72",
|
||||
"sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1",
|
||||
"sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1"
|
||||
"sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c",
|
||||
"sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86",
|
||||
"sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b",
|
||||
"sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd",
|
||||
"sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc",
|
||||
"sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea",
|
||||
"sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e",
|
||||
"sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308",
|
||||
"sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406",
|
||||
"sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d",
|
||||
"sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707",
|
||||
"sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d",
|
||||
"sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c",
|
||||
"sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.761"
|
||||
"version": "==0.782"
|
||||
},
|
||||
"mypy-extensions": {
|
||||
"hashes": [
|
||||
@@ -235,10 +235,10 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
|
||||
"sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
|
||||
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
|
||||
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
|
||||
],
|
||||
"version": "==20.1"
|
||||
"version": "==20.4"
|
||||
},
|
||||
"pefile": {
|
||||
"hashes": [
|
||||
@@ -249,27 +249,27 @@
|
||||
},
|
||||
"psutil": {
|
||||
"hashes": [
|
||||
"sha256:094f899ac3ef72422b7e00411b4ed174e3c5a2e04c267db6643937ddba67a05b",
|
||||
"sha256:10b7f75cc8bd676cfc6fa40cd7d5c25b3f45a0e06d43becd7c2d2871cbb5e806",
|
||||
"sha256:1b1575240ca9a90b437e5a40db662acd87bbf181f6aa02f0204978737b913c6b",
|
||||
"sha256:21231ef1c1a89728e29b98a885b8e0a8e00d09018f6da5cdc1f43f988471a995",
|
||||
"sha256:28f771129bfee9fc6b63d83a15d857663bbdcae3828e1cb926e91320a9b5b5cd",
|
||||
"sha256:70387772f84fa5c3bb6a106915a2445e20ac8f9821c5914d7cbde148f4d7ff73",
|
||||
"sha256:b560f5cd86cf8df7bcd258a851ca1ad98f0d5b8b98748e877a0aec4e9032b465",
|
||||
"sha256:b74b43fecce384a57094a83d2778cdfc2e2d9a6afaadd1ebecb2e75e0d34e10d",
|
||||
"sha256:e85f727ffb21539849e6012f47b12f6dd4c44965e56591d8dec6e8bc9ab96f4a",
|
||||
"sha256:fd2e09bb593ad9bdd7429e779699d2d47c1268cbde4dda95fcd1bd17544a0217",
|
||||
"sha256:ffad8eb2ac614518bbe3c0b8eb9dffdb3a8d2e3a7d5da51c5b974fb723a5c5aa"
|
||||
"sha256:0ee3c36428f160d2d8fce3c583a0353e848abb7de9732c50cf3356dd49ad63f8",
|
||||
"sha256:10512b46c95b02842c225f58fa00385c08fa00c68bac7da2d9a58ebe2c517498",
|
||||
"sha256:4080869ed93cce662905b029a1770fe89c98787e543fa7347f075ade761b19d6",
|
||||
"sha256:5e9d0f26d4194479a13d5f4b3798260c20cecf9ac9a461e718eb59ea520a360c",
|
||||
"sha256:66c18ca7680a31bf16ee22b1d21b6397869dda8059dbdb57d9f27efa6615f195",
|
||||
"sha256:68d36986ded5dac7c2dcd42f2682af1db80d4bce3faa126a6145c1637e1b559f",
|
||||
"sha256:90990af1c3c67195c44c9a889184f84f5b2320dce3ee3acbd054e3ba0b4a7beb",
|
||||
"sha256:a5b120bb3c0c71dfe27551f9da2f3209a8257a178ed6c628a819037a8df487f1",
|
||||
"sha256:d8a82162f23c53b8525cf5f14a355f5d1eea86fa8edde27287dd3a98399e4fdf",
|
||||
"sha256:f2018461733b23f308c298653c8903d32aaad7873d25e1d228765e91ae42c3f2",
|
||||
"sha256:ff1977ba1a5f71f89166d5145c3da1cea89a0fdb044075a12c720ee9123ec818"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.6.7"
|
||||
"version": "==5.7.2"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
|
||||
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
|
||||
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
||||
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
|
||||
],
|
||||
"version": "==2.5.2"
|
||||
"version": "==2.6.1"
|
||||
},
|
||||
"pyinstaller": {
|
||||
"hashes": [
|
||||
@@ -280,25 +280,25 @@
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
|
||||
"sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
|
||||
"sha256:7dd78437f2d8d019717dbf287772d0b2dbdfd13fc016aa7faa08d67bccc46adc",
|
||||
"sha256:d0ece7d223fe422088b0e8f13fa0a1e8eb745ebffcb8ed53d3e95394b6101a1c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.4"
|
||||
"version": "==2.5.3"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
|
||||
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
|
||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"version": "==2.4.6"
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
|
||||
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
|
||||
],
|
||||
"version": "==2019.3"
|
||||
"version": "==2020.1"
|
||||
},
|
||||
"pywin32-ctypes": {
|
||||
"hashes": [
|
||||
@@ -310,18 +310,18 @@
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.22.0"
|
||||
"version": "==2.24.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
@@ -332,32 +332,32 @@
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159",
|
||||
"sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"
|
||||
"sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66",
|
||||
"sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.1"
|
||||
"version": "==2.4.4"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
"sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897",
|
||||
"sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"
|
||||
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
|
||||
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-devhelp": {
|
||||
"hashes": [
|
||||
"sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34",
|
||||
"sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"
|
||||
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
|
||||
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-htmlhelp": {
|
||||
"hashes": [
|
||||
"sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422",
|
||||
"sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"
|
||||
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
|
||||
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-jsmath": {
|
||||
"hashes": [
|
||||
@@ -368,17 +368,24 @@
|
||||
},
|
||||
"sphinxcontrib-qthelp": {
|
||||
"hashes": [
|
||||
"sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20",
|
||||
"sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"
|
||||
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
|
||||
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-serializinghtml": {
|
||||
"hashes": [
|
||||
"sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227",
|
||||
"sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"
|
||||
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
|
||||
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
|
||||
],
|
||||
"version": "==1.1.3"
|
||||
"version": "==1.1.4"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
|
||||
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
|
||||
],
|
||||
"version": "==0.10.1"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
@@ -404,28 +411,29 @@
|
||||
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
|
||||
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
|
||||
],
|
||||
"markers": "implementation_name == 'cpython' and python_version < '3.8'",
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2",
|
||||
"sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d",
|
||||
"sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"
|
||||
"sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5",
|
||||
"sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae",
|
||||
"sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"
|
||||
],
|
||||
"version": "==3.7.4.1"
|
||||
"version": "==3.7.4.2"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
|
||||
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
|
||||
],
|
||||
"version": "==1.25.8"
|
||||
"version": "==1.25.10"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
"sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
|
||||
"sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
|
||||
],
|
||||
"version": "==1.11.2"
|
||||
"version": "==1.12.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -27,8 +27,9 @@
|
||||
:alt: Arch User Repository Package
|
||||
:target: https://aur.archlinux.org/packages/instaloader/
|
||||
|
||||
.. |downloads| image:: https://img.shields.io/pypi/dm/instaloader.svg
|
||||
.. |downloads| image:: https://pepy.tech/badge/instaloader/month
|
||||
:alt: PyPI Download Count
|
||||
:target: https://pepy.tech/project/instaloader/month
|
||||
|
||||
.. badges-end
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import subprocess
|
||||
def __main():
|
||||
with contextlib.suppress(AttributeError, psutil.Error):
|
||||
if psutil.Process().parent().parent().name() == "explorer.exe":
|
||||
subprocess.Popen("powershell -NoExit -Command \\\"& {0}\\\"".format(sys.argv[0]))
|
||||
subprocess.Popen("powershell -NoExit -Command \\\"& '{0}'\\\"".format(sys.argv[0]))
|
||||
return
|
||||
main()
|
||||
|
||||
@@ -44,7 +44,7 @@ with open('__main__.py', 'w+') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
# install dependencies and invoke PyInstaller
|
||||
commands = ["pip install pipenv==2018.11.26",
|
||||
commands = ["pip install pipenv==2020.6.2",
|
||||
"pipenv sync --dev",
|
||||
"pipenv run pyinstaller --log-level=DEBUG instaloader.spec"]
|
||||
|
||||
|
||||
-7
File diff suppressed because one or more lines are too long
Vendored
-7
File diff suppressed because one or more lines are too long
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
@import url(bootstrap-4.1.3.min.css);
|
||||
a {
|
||||
color: #008d06; }
|
||||
a:hover, a.current, a.active {
|
||||
color: #f48400; }
|
||||
a.current, a.active {
|
||||
font-weight: 500; }
|
||||
|
||||
.doc-sidebar a:hover, .modal-body a:hover {
|
||||
text-decoration: none; }
|
||||
|
||||
code {
|
||||
color: #212529; }
|
||||
|
||||
.highlight {
|
||||
border-radius: 0.3em; }
|
||||
.highlight pre {
|
||||
padding: 0.7em;
|
||||
color: #fff; }
|
||||
.highlight .c1 {
|
||||
color: #666; }
|
||||
.highlight .k, .highlight .kn, .highlight .ow {
|
||||
color: #008d06; }
|
||||
.highlight .nb, .highlight .ne, .highlight .nf, .highlight .vm {
|
||||
color: #f48400; }
|
||||
.highlight .s1, .highlight .s2, .highlight .sa, .highlight .se, .highlight .si, .highlight .mi {
|
||||
color: #ff7efd; }
|
||||
.highlight .c1, .highlight .nf, .highlight .sa, .highlight .se, .highlight .si {
|
||||
font-style: italic; }
|
||||
.highlight .k, .highlight .kn, .highlight .ow {
|
||||
font-weight: bold; }
|
||||
|
||||
.doc-content {
|
||||
padding-top: 0.5em;
|
||||
top: 3.5625em;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
overflow-y: auto; }
|
||||
|
||||
.doc-sidebar {
|
||||
position: fixed;
|
||||
padding-top: 0.5em;
|
||||
top: 3.5625em;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow-y: auto; }
|
||||
.doc-sidebar ul {
|
||||
list-style-type: none;
|
||||
padding-left: 1em; }
|
||||
.doc-sidebar ul.current {
|
||||
padding-left: 0; }
|
||||
.doc-sidebar li {
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em; }
|
||||
|
||||
.admonition {
|
||||
border-radius: 0.3em;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f8f9fa; }
|
||||
.admonition .admonition-title {
|
||||
color: #ff7efd; }
|
||||
|
||||
.topic {
|
||||
border-radius: 0.3em;
|
||||
background-color: #f8f9fa; }
|
||||
|
||||
.nav-link {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0; }
|
||||
|
||||
.btn-success {
|
||||
background-color: #008d06;
|
||||
border-color: #008d06; }
|
||||
.btn-success:hover {
|
||||
background-color: #f48400;
|
||||
border-color: #f48400; }
|
||||
|
||||
/*# sourceMappingURL=instaloader.css.map */
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
$('.doc-sidebar > ul > li.current').attr("id", "localtoc");
|
||||
$('#localtoc ul').addClass("nav flex-column");
|
||||
$('#localtoc ul li').addClass("nav-item");
|
||||
$('#localtoc ul li a').addClass("nav-link");
|
||||
$('.doc-content').scrollspy({target: '#localtoc'});
|
||||
|
||||
const top_href = '#' + $('.section:first').attr("id");
|
||||
$('#localtoc > a.current').attr("href", top_href);
|
||||
|
||||
$('#navbarToc a').on("click", function () {
|
||||
const href = $(this).attr("href");
|
||||
if (href === '#') {
|
||||
window.location.href = top_href;
|
||||
} else {
|
||||
window.location.href = href;
|
||||
}
|
||||
$('#navbarToc').modal('hide');
|
||||
});
|
||||
});
|
||||
Vendored
-124
@@ -1,124 +0,0 @@
|
||||
@import 'bootstrap-4.1.3.min.css';
|
||||
|
||||
$color_instaloader_main: #008d06;
|
||||
$color_instaloader_accent: #f48400;
|
||||
$color_instaloader_hyper: #ff7efd;
|
||||
$color_normal_text: #212529;
|
||||
$color_light_bg: #f8f9fa;
|
||||
|
||||
a {
|
||||
color: $color_instaloader_main;
|
||||
|
||||
&:hover, &.current, &.active {
|
||||
color: $color_instaloader_accent;
|
||||
}
|
||||
|
||||
&.current, &.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-sidebar, .modal-body {
|
||||
a:hover {
|
||||
text-decoration: none
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
color: $color_normal_text;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
border-radius: 0.3em;
|
||||
|
||||
pre {
|
||||
padding: 0.7em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.k, .kn, .ow {
|
||||
color: $color_instaloader_main
|
||||
}
|
||||
|
||||
.nb, .ne, .nf, .vm {
|
||||
color: $color_instaloader_accent
|
||||
}
|
||||
|
||||
.s1, .s2, .sa, .se, .si, .mi {
|
||||
color: $color_instaloader_hyper
|
||||
}
|
||||
|
||||
.c1, .nf, .sa, .se, .si {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.k, .kn, .ow {
|
||||
font-weight: bold
|
||||
}
|
||||
}
|
||||
|
||||
.doc-content {
|
||||
padding-top: 0.5em;
|
||||
top: 3.5625em;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.doc-sidebar {
|
||||
position: fixed;
|
||||
padding-top: 0.5em;
|
||||
top: 3.5625em;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow-y: auto;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding-left: 1em;
|
||||
|
||||
&.current {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.admonition {
|
||||
border-radius: 0.3em;
|
||||
border: 1px solid #ccc;
|
||||
background-color: $color_light_bg;
|
||||
|
||||
.admonition-title {
|
||||
color: $color_instaloader_hyper;
|
||||
}
|
||||
}
|
||||
|
||||
.topic {
|
||||
border-radius: 0.3em;
|
||||
background-color: $color_light_bg;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: $color_instaloader_main;
|
||||
border-color: $color_instaloader_main;
|
||||
|
||||
&:hover {
|
||||
background-color: $color_instaloader_accent;
|
||||
border-color: $color_instaloader_accent;
|
||||
}
|
||||
}
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+50
File diff suppressed because one or more lines are too long
Vendored
+46
-78
@@ -1,9 +1,6 @@
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% set html5_doctype = True %}
|
||||
|
||||
{% set script_files = script_files + ['_static/bootstrap-4.1.3.bundle.min.js', '_static/instaloader.js'] %}
|
||||
|
||||
{% set script_files = script_files + ['_static/instaloaderdoc.js'] %}
|
||||
{% block htmltitle %}
|
||||
{% if pagename == "index" %}
|
||||
<title>{{ title|striptags|e }}</title>
|
||||
@@ -11,7 +8,6 @@
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ super() }}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
@@ -22,94 +18,66 @@
|
||||
<link rel="canonical" href="https://instaloader.github.io/{{ pagename }}.html"/>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<nav class="navbar navbar-expand-sm navbar-light bg-light fixed-top border-bottom">
|
||||
<nav class="navbar navbar-light navbar-expand bg-light border-bottom">
|
||||
<a class="navbar-brand" href="{{ pathto(master_doc) }}">
|
||||
<img src="{{ pathto('_static/' + logo, 1) }}" width="30" height="30" class="d-inline-block align-top" alt="logo">
|
||||
Instaloader
|
||||
<span class="ml-3">Instaloader</span>
|
||||
</a>
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item ml-0 ml-sm-2 ml-md-0">
|
||||
<a target="_blank" class="nav-link"
|
||||
<li class="nav-item ml-0 ml-sm-2">
|
||||
<a class="nav-link"
|
||||
href="https://github.com/instaloader/instaloader/releases/tag/v{{ current_release }}"
|
||||
title="Released on {{ current_release_date }}" data-toggle="tooltip">v<b>{{ current_release }}</b></a>
|
||||
title="Released on {{ current_release_date }}" data-toggle="tooltip">
|
||||
v<b>{{ current_release }}</b>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarLinks"
|
||||
aria-controls="navbarLinks" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<ul class="navbar-nav ml-auto d-none d-sm-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/instaloader/instaloader/issues">
|
||||
Issues
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/instaloader/instaloader/releases">
|
||||
Releases
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/instaloader/instaloader">
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="d-inline-block d-md-none btn btn-outline-primary btn-sm" type="button"
|
||||
data-toggle="collapse" data-target="#instaloaderdocSidebar" aria-controls="instaloaderdocSidebar"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarLinks">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a target="_blank" class="nav-link" href="https://github.com/instaloader/instaloader/issues">Issues</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a target="_blank" class="nav-link" href="https://github.com/instaloader/instaloader/releases">Releases</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a target="_blank" class="nav-link" href="https://github.com/instaloader/instaloader">GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="form-inline" action="search.html" method="get">
|
||||
<div class="input-group my-2 my-sm-0 ml-2">
|
||||
<input class="form-control" type="search" placeholder="Search" aria-label="Search" name="q">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-success btn-small" type="submit">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% if pagename != "index" %}
|
||||
<button type="button" class="btn btn-success d-md-none btn-small ml-2" data-toggle="modal"
|
||||
data-target="#navbarToc">
|
||||
TOC
|
||||
</button>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% if pagename != "index" %}
|
||||
<div class="modal fade" id="navbarToc" tabindex="-1" role="dialog"
|
||||
aria-hidden="true" aria-labelledby="navbarTocTitle">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="navbarTocTitle">Contents</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ toctree(maxdepth=-1) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container-fluid row">
|
||||
<div class="col-12 {% if pagename != "index" %}col-md-9 col-xl-10{% endif %} doc-content">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
{% if pagename != "index" %}
|
||||
<div class="d-none col-md-3 col-xl-2 bg-light doc-sidebar d-md-flex flex-column border-left">
|
||||
|
||||
<h3>{{ _('Table of Contents') }}</h3>
|
||||
{{ toctree(maxdepth=-1) }}
|
||||
|
||||
<div class="d-flex justify-content-between small mt-auto mb-2">
|
||||
<a class="reference internal{% if pagename == "genindex" %} current{% endif %}" href="genindex.html">Index</a>
|
||||
{% if pagename != "genindex" and pagename != "search" %}
|
||||
<a class="reference external"
|
||||
href="https://github.com/instaloader/instaloader/edit/master/docs/{{ pagename }}.rst">Edit this page</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="instaloaderdoc-sidebar collapse d-md-block col-md-3 col-xl-2 bg-light"
|
||||
id="instaloaderdocSidebar">
|
||||
<nav role="navigation" aria-label="Table of Contents">
|
||||
<form action="{{ pathto('search') }}" method="get" role="search" class="border-bottom mr-2 pb-2 mb-2 px-1">
|
||||
<label class="sr-only" for="searchQueryInput">Search</label>
|
||||
<input type="search" name="q" id="searchQueryInput" class="form-control my-2"
|
||||
placeholder="Search..." aria-label="Search">
|
||||
</form>
|
||||
<h4>Contents</h4>
|
||||
{{ toctree(maxdepth=-1) }}
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
<main class="col-12 col-md-9 col-xl-10 pt-2" role="main">
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
+14
-164
@@ -12,11 +12,8 @@ Python Module :mod:`instaloader`
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. contents::
|
||||
:backlinks: none
|
||||
|
||||
Instaloader exposes its internally used methods and structures as a Python
|
||||
module, making it a **powerful and intuitive Python API for Instagram**,
|
||||
module, making it a powerful and intuitive Python API for Instagram,
|
||||
allowing to further customize obtaining media and metadata.
|
||||
|
||||
Start with getting an instance of :class:`Instaloader`::
|
||||
@@ -85,173 +82,26 @@ metadata of a Profile. :class:`Profile` instances can be created with:
|
||||
- :meth:`Profile.get_followers`
|
||||
Profiles that follow given user.
|
||||
|
||||
- :attr:`Post.owner_profile`, :attr:`Story.owner_profile` and :attr:`StoryItem.owner_profile`
|
||||
- :attr:`Post.owner_profile`, :attr:`Story.owner_profile`, :attr:`StoryItem.owner_profile`
|
||||
Owner profile of particular object.
|
||||
|
||||
- :meth:`Post.get_likes`
|
||||
Profiles that liked a given :class:`Post`
|
||||
|
||||
- :attr:`PostComment.owner` attribute for comment in :meth:`Post.get_comments`
|
||||
- :attr:`PostComment.owner`
|
||||
Profile of a Post comment.
|
||||
|
||||
A reference of the many methods provided by the :mod:`instaloader` module is
|
||||
provided in the remainder of this document.
|
||||
For a list of a few code examples that use the Instaloader module, see
|
||||
:ref:`codesnippets`.
|
||||
|
||||
For a list of real code examples using the Instaloader module for advanced
|
||||
tasks, see :ref:`codesnippets`.
|
||||
The reference of the classes and functions provided by the :mod:`instaloader` module is
|
||||
divided into the following subsections:
|
||||
|
||||
``Instaloader`` (Main Class)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
.. autoclass:: Instaloader
|
||||
:no-show-inheritance:
|
||||
|
||||
Instagram Structures
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Posts
|
||||
"""""
|
||||
|
||||
.. autoclass:: Post
|
||||
:no-show-inheritance:
|
||||
|
||||
Additionally, the following trivial structures are defined:
|
||||
|
||||
.. autoclass:: PostSidecarNode
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: PostComment
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: PostCommentAnswer
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: PostLocation
|
||||
:no-show-inheritance:
|
||||
|
||||
User Stories
|
||||
""""""""""""
|
||||
|
||||
.. autoclass:: Story
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: StoryItem
|
||||
:no-show-inheritance:
|
||||
|
||||
Highlights
|
||||
""""""""""
|
||||
|
||||
.. autoclass:: Highlight
|
||||
:no-show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
Bases: :class:`Story`
|
||||
|
||||
.. versionadded:: 4.1
|
||||
|
||||
Profiles
|
||||
""""""""
|
||||
|
||||
.. autoclass:: Profile
|
||||
:no-show-inheritance:
|
||||
|
||||
Hashtags
|
||||
""""""""
|
||||
|
||||
.. autoclass:: Hashtag
|
||||
:no-show-inheritance:
|
||||
|
||||
.. versionadded:: 4.4
|
||||
|
||||
TopSearchResults
|
||||
""""""""""""""""
|
||||
|
||||
.. autoclass:: TopSearchResults
|
||||
:no-show-inheritance:
|
||||
|
||||
.. versionadded:: 4.3
|
||||
|
||||
Loading and Saving
|
||||
""""""""""""""""""
|
||||
|
||||
:class:`Post`, :class:`StoryItem`, :class:`Profile` and :class:`Hashtag` can be saved and loaded
|
||||
to/from JSON files.
|
||||
|
||||
.. autofunction:: load_structure_from_file
|
||||
|
||||
.. autofunction:: save_structure_to_file
|
||||
|
||||
Exceptions
|
||||
^^^^^^^^^^
|
||||
|
||||
.. currentmodule:: instaloader.exceptions
|
||||
|
||||
.. autoexception:: InstaloaderException
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoexception:: ConnectionException
|
||||
|
||||
.. currentmodule:: instaloader
|
||||
|
||||
.. autoexception:: QueryReturnedBadRequestException
|
||||
|
||||
.. autoexception:: QueryReturnedForbiddenException
|
||||
|
||||
.. autoexception:: ProfileNotExistsException
|
||||
|
||||
.. autoexception:: ProfileHasNoPicsException
|
||||
|
||||
.. autoexception:: PrivateProfileNotFollowedException
|
||||
|
||||
.. autoexception:: LoginRequiredException
|
||||
|
||||
.. autoexception:: TwoFactorAuthRequiredException
|
||||
|
||||
.. versionadded:: 4.2
|
||||
|
||||
.. autoexception:: InvalidArgumentException
|
||||
|
||||
.. autoexception:: BadResponseException
|
||||
|
||||
.. autoexception:: BadCredentialsException
|
||||
|
||||
.. autoexception:: PostChangedException
|
||||
|
||||
.. autoexception:: QueryReturnedNotFoundException
|
||||
|
||||
.. versionchanged:: 4.3
|
||||
QueryReturnedNotFoundException now inherits ConnectionException
|
||||
to retry on 404 errors.
|
||||
|
||||
|
||||
.. autoexception:: TooManyRequestsException
|
||||
|
||||
Resumable Iterations
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
.. autoclass:: NodeIterator
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: FrozenNodeIterator
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autofunction:: resumable_iteration
|
||||
|
||||
``InstaloaderContext`` (Low-level functions)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``InstaloaderContext``
|
||||
""""""""""""""""""""""
|
||||
|
||||
.. autoclass:: InstaloaderContext
|
||||
:no-show-inheritance:
|
||||
|
||||
``RateController``
|
||||
""""""""""""""""""
|
||||
|
||||
.. autoclass:: RateController
|
||||
:no-show-inheritance:
|
||||
|
||||
.. versionadded:: 4.5
|
||||
module/instaloader
|
||||
module/structures
|
||||
module/nodeiterator
|
||||
module/instaloadercontext
|
||||
module/exceptions
|
||||
|
||||
+17
-16
@@ -77,7 +77,7 @@ Instaloader supports the following targets:
|
||||
instructs Instaloader to also **download the user's stories**,
|
||||
|
||||
- :option:`--highlights`
|
||||
to **download highlights of each profile that is downloaded**,
|
||||
to **download the highlights of that profile**,
|
||||
|
||||
- :option:`--tagged`
|
||||
to **download posts where the user is tagged**, and
|
||||
@@ -86,7 +86,7 @@ Instaloader supports the following targets:
|
||||
to **download IGTV videos**.
|
||||
|
||||
- ``"#hashtag"``
|
||||
Posts with a certain **hashtag** (the quotes are usually necessary),
|
||||
Posts with a certain **hashtag** (the quotes are usually necessary).
|
||||
|
||||
- ``%location id``
|
||||
Posts tagged with a given location; the location ID is the numerical ID
|
||||
@@ -98,20 +98,20 @@ Instaloader supports the following targets:
|
||||
|
||||
- ``:stories``
|
||||
The currently-visible **stories** of your followees (requires
|
||||
:option:`--login`),
|
||||
:option:`--login`).
|
||||
|
||||
- ``:feed``
|
||||
Your **feed** (requires :option:`--login`),
|
||||
Your **feed** (requires :option:`--login`).
|
||||
|
||||
- ``:saved``
|
||||
Posts which are marked as **saved** (requires :option:`--login`),
|
||||
Posts which are marked as **saved** (requires :option:`--login`).
|
||||
|
||||
- ``@profile``
|
||||
All profiles that are followed by ``profile``, i.e. the *followees* of
|
||||
``profile`` (requires :option:`--login`).
|
||||
|
||||
- ``-post``
|
||||
Replace **post** with the post's shortcode to download single post. Must be preceeded by ``--`` in
|
||||
Replace **post** with the post's shortcode to download single post. Must be preceded by ``--`` in
|
||||
the argument list to not be mistaken as an option flag::
|
||||
|
||||
instaloader -- -B_K4CykAOtf
|
||||
@@ -196,7 +196,7 @@ Filter Posts
|
||||
.. py:currentmodule:: instaloader
|
||||
|
||||
The options :option:`--post-filter` and :option:`--storyitem-filter`
|
||||
allows to specify criteria that posts or story items have to
|
||||
allow to specify criteria that posts or story items have to
|
||||
meet to be downloaded. If not given, all posts are downloaded.
|
||||
|
||||
The filter string must be a
|
||||
@@ -208,7 +208,7 @@ Id est, the following attributes can be used with both
|
||||
:option:`--post-filter` and :option:`--storyitem-filter`:
|
||||
|
||||
- :attr:`~Post.owner_username` (str), :attr:`~Post.owner_id` (int)
|
||||
Owner profile username / userid.
|
||||
Owner profile username / user ID.
|
||||
|
||||
- :attr:`~Post.date_utc` (datetime), :attr:`~Post.date_local` (datetime)
|
||||
Creation timestamp. Since :class:`~datetime.datetime` objects can be created
|
||||
@@ -217,7 +217,7 @@ Id est, the following attributes can be used with both
|
||||
instaloader --post-filter="date_utc <= datetime(2018, 5, 31)" target
|
||||
|
||||
- :attr:`~Post.is_video` (bool)
|
||||
Post/StoryItem is a video. For example, you may skip videos::
|
||||
Whether Post/StoryItem is a video. For example, you may skip videos::
|
||||
|
||||
instaloader --post-filter="not is_video" target
|
||||
|
||||
@@ -234,8 +234,8 @@ As :option:`--post-filter`, the following attributes can be used additionally:
|
||||
instaloader --login=your_username --post-filter=viewer_has_liked :feed
|
||||
|
||||
- :attr:`~Post.likes` (int), :attr:`~Post.comments` (int)
|
||||
Likes count / Comments count. You might only want to download posts that
|
||||
either you liked or were liked by many others**::
|
||||
Likes count / comments count. You might only want to download posts that
|
||||
were either liked by yourself or by many others::
|
||||
|
||||
instaloader --login=your_username --post-filter="likes>100 or viewer_has_liked" profile
|
||||
|
||||
@@ -265,7 +265,7 @@ along with each post where the Post's caption is saved.
|
||||
You can customize what metadata to save for each Post or StoryItem with
|
||||
:option:`--post-metadata-txt` and :option:`--storyitem-metadata-txt`. The
|
||||
default is ``--post-metadata-txt={caption}`` and no storyitem metadata txt.
|
||||
These strings are formatted similar as in the :ref:`filename-specification` and
|
||||
These strings are formatted similar as the path patterns described in :ref:`filename-specification` and
|
||||
the result is saved in text files, unless it is empty.
|
||||
|
||||
Specifying these options multiple times results in output having multiple lines,
|
||||
@@ -299,10 +299,10 @@ Instaloader logged-in, create a session file::
|
||||
|
||||
instaloader --login=your_username
|
||||
|
||||
Then use the same parameter in your cronjob to load the session and download
|
||||
Then use the same username in your cronjob to load the session and download
|
||||
the given targets::
|
||||
|
||||
instaloader --login=your_username --quiet <target> [...]
|
||||
instaloader --login=your_username --quiet target [...]
|
||||
|
||||
Instaloader saves the session file to
|
||||
``~/.config/instaloader/session-YOUR-USERNAME``. See
|
||||
@@ -317,5 +317,6 @@ Instaloader exposes its internally used methods and structures, making it a
|
||||
powerful and intuitive Python API for Instagram, allowing to further customize
|
||||
obtaining media and metadata.
|
||||
|
||||
Also see :ref:`codesnippets`, where we collect example scripts that use
|
||||
Instaloader to achieve more complex tasks.
|
||||
Also see :ref:`codesnippets`, where we collect a few example scripts that use
|
||||
Instaloader for simple tasks that cannot be done with the command line
|
||||
interface.
|
||||
|
||||
+12
-46
@@ -36,7 +36,7 @@ stores all posts along with the pictures's captions there.
|
||||
Re-Download the given object
|
||||
|
||||
- ``+args.txt``
|
||||
Read targets (and options) from given textfile. See :option:`+args.txt`.
|
||||
Read targets (and options) from given text file. See :option:`+args.txt`.
|
||||
|
||||
What to Download of each Post
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -59,7 +59,7 @@ What to Download of each Post
|
||||
|
||||
.. option:: --geotags, -G
|
||||
|
||||
**Download geotags** when available. Geotags are stored as a text file with
|
||||
Download geotags when available. Geotags are stored as a text file with
|
||||
the location's name and a Google Maps link. This requires an additional
|
||||
request to the Instagram server for each picture. Requires :option:`--login`.
|
||||
|
||||
@@ -94,13 +94,6 @@ What to Download of each Post
|
||||
What to Download of each Profile
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. option:: --profile-pic-only, -P
|
||||
|
||||
.. deprecated:: 4.1
|
||||
Use :option:`--no-posts`.
|
||||
|
||||
Only download profile picture.
|
||||
|
||||
.. option:: --no-posts
|
||||
|
||||
Do not download regular posts.
|
||||
@@ -113,12 +106,12 @@ What to Download of each Profile
|
||||
|
||||
.. option:: --stories, -s
|
||||
|
||||
Also **download stories** of each profile that is downloaded. Requires
|
||||
Also download stories of each profile that is downloaded. Requires
|
||||
:option:`--login`.
|
||||
|
||||
.. option:: --highlights
|
||||
|
||||
Also **download highlights** of each profile that is downloaded. Requires
|
||||
Also download highlights of each profile that is downloaded. Requires
|
||||
:option:`--login`.
|
||||
|
||||
.. versionadded:: 4.1
|
||||
@@ -135,20 +128,6 @@ What to Download of each Profile
|
||||
|
||||
.. versionadded:: 4.3
|
||||
|
||||
.. option:: --stories-only
|
||||
|
||||
.. deprecated:: 4.1
|
||||
Use :option:`--stories` :option:`--no-posts`.
|
||||
|
||||
Rather than downloading regular posts of each specified profile, only
|
||||
download stories. Requires :option:`--login`. Does not imply
|
||||
:option:`--no-profile-pic`.
|
||||
|
||||
.. note::
|
||||
|
||||
If possible, use ``:stories`` target rather than :option:`--stories-only`
|
||||
with all your followees. ``:stories`` uses fewer API requests.
|
||||
|
||||
Which Posts to Download
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -182,7 +161,7 @@ Which Posts to Download
|
||||
Login (Download Private Profiles)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Instaloader can **login to Instagram**. This allows downloading private
|
||||
Instaloader can login to Instagram. This allows downloading private
|
||||
profiles. To login, pass the :option:`--login` option. Your session cookie (not your
|
||||
password!) will be saved to a local file to be reused next time you want
|
||||
Instaloader to login.
|
||||
@@ -257,19 +236,6 @@ How to Download
|
||||
to ``3``. If a connection fails, it can be manually skipped by hitting
|
||||
:kbd:`Control-c`. Set this to ``0`` to retry infinitely.
|
||||
|
||||
.. option:: --commit-mode
|
||||
|
||||
Tries to ensure downloaded images avoid corruption in case of unexpected
|
||||
interruption. If the last picture is corrupted, Instaloader will fix the
|
||||
picture the next time it is run.
|
||||
|
||||
.. versionadded:: 4.2
|
||||
|
||||
.. warning::
|
||||
We discourage from using the "commit mode" due to its bugs when being used
|
||||
with other command line options (e.g. :issue:`257` with
|
||||
:option:`--fast-update` and :issue:`483` with :option:`--tagged`).
|
||||
|
||||
.. option:: --request-timeout N
|
||||
|
||||
Seconds to wait before timing out a connection request.
|
||||
@@ -289,18 +255,18 @@ Miscellaneous Options
|
||||
|
||||
Read arguments from file `args.txt`, a shortcut to provide arguments from
|
||||
file rather than command-line. This provides a convenient way to hide login
|
||||
info from CLI, and can also be used to simplify managment of long arguments.
|
||||
info from CLI, and can also be used to simplify management of long arguments.
|
||||
|
||||
.. note::
|
||||
|
||||
Text file should separate arguments with line breaks.
|
||||
|
||||
args.txt example::
|
||||
args.txt example::
|
||||
|
||||
--login=MYUSERNAME
|
||||
--password=MYPASSWORD
|
||||
--fast-update
|
||||
profile1
|
||||
profile2
|
||||
--login=MYUSERNAME
|
||||
--password=MYPASSWORD
|
||||
--fast-update
|
||||
profile1
|
||||
profile2
|
||||
|
||||
.. versionadded:: 4.1
|
||||
|
||||
+8
-33
@@ -42,7 +42,7 @@ The code example with :func:`~itertools.dropwhile` and
|
||||
:func:`~itertools.takewhile` makes the assumption that the post iterator returns
|
||||
posts in exact chronological order. As discussed in :issue:`666`, the following
|
||||
approach fits for an **almost chronological order**, where up to *k* older posts
|
||||
are inserted into an otherwise chronological order, such as an Hashtag feed.
|
||||
are inserted into an otherwise chronological order, such as a Hashtag feed.
|
||||
|
||||
.. literalinclude:: codesnippets/666_historical_hashtag_data.py
|
||||
|
||||
@@ -50,7 +50,7 @@ Likes of a Profile / Ghost Followers
|
||||
------------------------------------
|
||||
|
||||
To obtain a list of your inactive followers, i.e. followers that did not like
|
||||
any of your pictures, into a file you can use this approach.
|
||||
any of your pictures, you can use this approach.
|
||||
|
||||
.. literalinclude:: codesnippets/120_ghost_followers.py
|
||||
|
||||
@@ -78,11 +78,11 @@ Discussed in :issue:`56`.
|
||||
Only one Post per User
|
||||
----------------------
|
||||
|
||||
To download only the one most recent post from each user, this snippet creates a
|
||||
:class:`set` that contains the users of which a post has already been
|
||||
downloaded. While iterating the posts, it checks whether the post's owner
|
||||
already is in the set. If not, the post is downloaded from Instagram and the
|
||||
user is added to that set.
|
||||
To download only the single most recent post per user within a hashtag feed,
|
||||
this snippet uses a :class:`set` that contains the users of whom a post has
|
||||
already been downloaded. For each post, it checks whether the post's creator is
|
||||
already contained in that set. If not, the post is downloaded from Instagram and
|
||||
the user is added to that set.
|
||||
|
||||
.. literalinclude:: codesnippets/113_only_one_per_user.py
|
||||
|
||||
@@ -94,37 +94,12 @@ Discussed in :issue:`113`.
|
||||
Top X Posts of User
|
||||
-------------------
|
||||
|
||||
With Instaloader, it is easy to download the few most-liked pictres of a user.
|
||||
With Instaloader, it is easy to download the few most-liked pictures of a user.
|
||||
|
||||
.. literalinclude:: codesnippets/194_top_x_of_user.py
|
||||
|
||||
Discussed in :issue:`194`.
|
||||
|
||||
Upgrade Images by Local Copies
|
||||
------------------------------
|
||||
|
||||
The following script finds local versions of images fetched by Instaloader, in
|
||||
order to upgrade the downloaded images by locally-found versions with better
|
||||
quality. It uses image hashing to identify similar images.
|
||||
|
||||
`updgrade-instaloader-images.py <https://gist.github.com/pavelkryukov/15f93d19a99428a284a8bcec27e0187b>`__ (external link to GitHub Gist)
|
||||
|
||||
Discussed in :issue:`46`.
|
||||
|
||||
Add Captions to Images
|
||||
----------------------
|
||||
|
||||
Instaloader does not modify the downloaded JPEG file. However, one could combine
|
||||
it with an imaging library such as Pillow or PIL to render the caption on
|
||||
Instagram pictures. The following shows an approach.
|
||||
|
||||
.. literalinclude:: codesnippets/110_pil_captions.py
|
||||
|
||||
See also :attr:`Post.caption`, :attr:`Post.url`, :meth:`Post.from_shortcode`,
|
||||
:func:`load_structure_from_file`.
|
||||
|
||||
Discussed in :issue:`110`.
|
||||
|
||||
Metadata JSON Files
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from io import BytesIO
|
||||
|
||||
from requests import get
|
||||
from PIL import Image, ImageDraw
|
||||
from instaloader import *
|
||||
|
||||
L = Instaloader()
|
||||
|
||||
# Load Post instance
|
||||
post = load_structure_from_file(L.context, '2017-10-01_18-53-03_UTC.json.xz')
|
||||
# or post = Post.from_shortcode(L.context, SHORTCODE)
|
||||
|
||||
# Render caption
|
||||
image = Image.open(BytesIO(get(post.url).content))
|
||||
draw = ImageDraw.Draw(image)
|
||||
color = 'rgb(0, 0, 0)' # black color
|
||||
draw.text((300,100), post.caption.encode('latin1', errors='ignore'), fill=color)
|
||||
|
||||
# Save image
|
||||
image.save('test.jpg')
|
||||
@@ -2,26 +2,26 @@ import instaloader
|
||||
|
||||
L = instaloader.Instaloader()
|
||||
|
||||
USER = 'your_account'
|
||||
USER = "your_account"
|
||||
PROFILE = USER
|
||||
|
||||
# Your preferred way of logging in:
|
||||
# Load session previously saved with `instaloader -l USERNAME`:
|
||||
L.load_session_from_file(USER)
|
||||
|
||||
profile = instaloader.Profile.from_username(L.context, PROFILE)
|
||||
|
||||
likes = set()
|
||||
print('Fetching likes of all posts of profile {}.'.format(profile.username))
|
||||
print("Fetching likes of all posts of profile {}.".format(profile.username))
|
||||
for post in profile.get_posts():
|
||||
print(post)
|
||||
likes = likes | set(post.get_likes())
|
||||
|
||||
print('Fetching followers of profile {}.'.format(profile.username))
|
||||
print("Fetching followers of profile {}.".format(profile.username))
|
||||
followers = set(profile.get_followers())
|
||||
|
||||
ghosts = followers - likes
|
||||
|
||||
print('Storing ghosts into file.')
|
||||
with open('/YOUR PATH/inactive-users.txt', 'w') as f:
|
||||
print("Storing ghosts into file.")
|
||||
with open("inactive-users.txt", 'w') as f:
|
||||
for ghost in ghosts:
|
||||
print(ghost.username, file=f)
|
||||
|
||||
@@ -9,7 +9,7 @@ SINCE = datetime(2020, 5, 10) # further from today, inclusive
|
||||
UNTIL = datetime(2020, 5, 11) # closer to today, not inclusive
|
||||
|
||||
k = 0 # initiate k
|
||||
k_list = [] # uncomment this to tune k
|
||||
#k_list = [] # uncomment this to tune k
|
||||
|
||||
for post in posts:
|
||||
postdate = post.date
|
||||
@@ -24,7 +24,8 @@ for post in posts:
|
||||
continue
|
||||
else:
|
||||
L.download_post(post, "#urbanphotography")
|
||||
k = 0 # set k to 0
|
||||
# if you want to tune k, uncomment below to get your k max
|
||||
#k_list.append(k)
|
||||
k = 0 # set k to 0
|
||||
|
||||
#max(k_list)
|
||||
|
||||
+1
-1
@@ -374,6 +374,6 @@ texinfo_documents = [
|
||||
|
||||
def setup(app):
|
||||
typing.TYPE_CHECKING = True
|
||||
app.add_stylesheet("instaloader.css")
|
||||
app.add_stylesheet("instaloaderdoc.css")
|
||||
app.connect('autodoc-process-signature', sphinx_autodoc_typehints.process_signature)
|
||||
app.connect('autodoc-process-docstring', sphinx_autodoc_typehints.process_docstring)
|
||||
|
||||
@@ -5,27 +5,27 @@ Install Instaloader
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
**To install Instaloader**,
|
||||
To **install Instaloader**,
|
||||
|
||||
#. Ensure that you have `Python <https://www.python.org/>`__, at least
|
||||
version 3.5 and `pip <https://pypi.python.org/pypi/pip>`__
|
||||
installed
|
||||
version 3.5, and `pip <https://pypi.python.org/pypi/pip>`__
|
||||
installed.
|
||||
|
||||
#. Then, install Instaloader using::
|
||||
|
||||
pip3 install instaloader
|
||||
|
||||
**To upgrade Instaloader**, do::
|
||||
To **upgrade Instaloader** to its current version, do::
|
||||
|
||||
pip3 install --upgrade instaloader
|
||||
|
||||
|
||||
**Alternative methods for installing Instaloader:**
|
||||
**Alternative methods** for installing Instaloader:
|
||||
|
||||
- If you do not want to use pip, even though it is highly recommended,
|
||||
and prefer to **install Instaloader manually**,
|
||||
`Download the Source <https://github.com/instaloader/instaloader/releases/latest>`__,
|
||||
extract the Zip or Tarball and execute ``instaloader.py`` from there.
|
||||
extract the Zip or Tarball and run ``setup.py`` from there.
|
||||
|
||||
- On **Arch Linux**, you may install Instaloader using the
|
||||
`Instaloader AUR package <https://aur.archlinux.org/packages/instaloader/>`__.
|
||||
@@ -33,6 +33,6 @@ Install Instaloader
|
||||
- On **Windows 10**, you may download the standalone executable from the
|
||||
`current release page <https://github.com/instaloader/instaloader/releases/latest>`__.
|
||||
|
||||
- To test the most current pre-release or development version of Instaloader::
|
||||
- To test the most current **pre-release** version of Instaloader::
|
||||
|
||||
pip3 install --pre instaloader
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
Exceptions
|
||||
^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. currentmodule:: instaloader.exceptions
|
||||
|
||||
.. autoexception:: InstaloaderException
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoexception:: ConnectionException
|
||||
|
||||
.. currentmodule:: instaloader
|
||||
|
||||
.. autoexception:: QueryReturnedBadRequestException
|
||||
|
||||
.. autoexception:: QueryReturnedForbiddenException
|
||||
|
||||
.. autoexception:: ProfileNotExistsException
|
||||
|
||||
.. autoexception:: ProfileHasNoPicsException
|
||||
|
||||
.. autoexception:: PrivateProfileNotFollowedException
|
||||
|
||||
.. autoexception:: LoginRequiredException
|
||||
|
||||
.. autoexception:: TwoFactorAuthRequiredException
|
||||
|
||||
.. versionadded:: 4.2
|
||||
|
||||
.. autoexception:: InvalidArgumentException
|
||||
|
||||
.. autoexception:: BadResponseException
|
||||
|
||||
.. autoexception:: BadCredentialsException
|
||||
|
||||
.. autoexception:: PostChangedException
|
||||
|
||||
.. autoexception:: QueryReturnedNotFoundException
|
||||
|
||||
.. versionchanged:: 4.3
|
||||
QueryReturnedNotFoundException now inherits ConnectionException
|
||||
to retry on 404 errors.
|
||||
|
||||
|
||||
.. autoexception:: TooManyRequestsException
|
||||
@@ -0,0 +1,10 @@
|
||||
``Instaloader`` (Main Class)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. autoclass:: Instaloader
|
||||
:no-show-inheritance:
|
||||
@@ -0,0 +1,24 @@
|
||||
``InstaloaderContext`` (Low-level functions)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. contents::
|
||||
:backlinks: none
|
||||
|
||||
``InstaloaderContext``
|
||||
""""""""""""""""""""""
|
||||
|
||||
.. autoclass:: InstaloaderContext
|
||||
:no-show-inheritance:
|
||||
|
||||
``RateController``
|
||||
""""""""""""""""""
|
||||
|
||||
.. autoclass:: RateController
|
||||
:no-show-inheritance:
|
||||
|
||||
.. versionadded:: 4.5
|
||||
@@ -0,0 +1,31 @@
|
||||
Resumable Iterations
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. contents::
|
||||
:backlinks: none
|
||||
|
||||
For many download targets, Instaloader is able to resume a
|
||||
previously-interrupted iteration. It provides an interruptible
|
||||
Iterator :class:`NodeIterator` and a context manager
|
||||
:func:`resumable_iteration`, which we both present here.
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
``NodeIterator``
|
||||
""""""""""""""""
|
||||
|
||||
.. autoclass:: NodeIterator
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: FrozenNodeIterator
|
||||
:no-show-inheritance:
|
||||
|
||||
``resumable_iteration``
|
||||
"""""""""""""""""""""""
|
||||
|
||||
.. autofunction:: resumable_iteration
|
||||
@@ -0,0 +1,82 @@
|
||||
Instagram Structures
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. module:: instaloader
|
||||
:noindex:
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. contents::
|
||||
:backlinks: none
|
||||
|
||||
Posts
|
||||
"""""
|
||||
|
||||
.. autoclass:: Post
|
||||
:no-show-inheritance:
|
||||
|
||||
Additionally, the following trivial structures are defined:
|
||||
|
||||
.. autoclass:: PostSidecarNode
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: PostComment
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: PostCommentAnswer
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: PostLocation
|
||||
:no-show-inheritance:
|
||||
|
||||
User Stories
|
||||
""""""""""""
|
||||
|
||||
.. autoclass:: Story
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoclass:: StoryItem
|
||||
:no-show-inheritance:
|
||||
|
||||
Highlights
|
||||
""""""""""
|
||||
|
||||
.. autoclass:: Highlight
|
||||
:no-show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
Bases: :class:`Story`
|
||||
|
||||
.. versionadded:: 4.1
|
||||
|
||||
Profiles
|
||||
""""""""
|
||||
|
||||
.. autoclass:: Profile
|
||||
:no-show-inheritance:
|
||||
|
||||
Hashtags
|
||||
""""""""
|
||||
|
||||
.. autoclass:: Hashtag
|
||||
:no-show-inheritance:
|
||||
|
||||
.. versionadded:: 4.4
|
||||
|
||||
TopSearchResults
|
||||
""""""""""""""""
|
||||
|
||||
.. autoclass:: TopSearchResults
|
||||
:no-show-inheritance:
|
||||
|
||||
.. versionadded:: 4.3
|
||||
|
||||
Loading and Saving
|
||||
""""""""""""""""""
|
||||
|
||||
:class:`Post`, :class:`StoryItem`, :class:`Profile`, :class:`Hashtag` and
|
||||
:class:`FrozenNodeIterator` can be saved and loaded to/from JSON files.
|
||||
|
||||
.. autofunction:: load_structure_from_file
|
||||
|
||||
.. autofunction:: save_structure_to_file
|
||||
@@ -1,2 +1,2 @@
|
||||
requests
|
||||
sphinx!=1.7.7
|
||||
sphinx~=2.3
|
||||
|
||||
+14
-14
@@ -5,8 +5,8 @@ Troubleshooting
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
429 - Too Many Requests
|
||||
-----------------------
|
||||
429 Too Many Requests
|
||||
---------------------
|
||||
|
||||
Instaloader has a logic to keep track of its requests to Instagram and to obey
|
||||
their rate limits. Since they are nowhere documented, we try them out
|
||||
@@ -14,21 +14,21 @@ experimentally. We have a daily cron job running to confirm that Instaloader
|
||||
still stays within the rate limits. Nevertheless, the rate control logic assumes
|
||||
that
|
||||
|
||||
- at one time, Instaloader is the only application that consumes requests. I.e.
|
||||
- at one time, Instaloader is the only application that consumes requests, i.e.
|
||||
neither the Instagram browser interface, nor a mobile app, nor another
|
||||
Instaloader instance is running in parallel,
|
||||
Instaloader instance is running in parallel, and
|
||||
|
||||
- no requests had been consumed when Instaloader starts.
|
||||
|
||||
The latter one implies that restarting or reinstantiating Instaloader often
|
||||
within short time is prone to cause a 429. When a request is denied with a 429,
|
||||
within short time is prone to cause a 429. If a request is denied with a 429,
|
||||
Instaloader retries the request as soon as the temporary ban is assumed to be
|
||||
expired. In case the retry continuously fails for some reason, which should not
|
||||
happen in normal conditions, consider adjusting the
|
||||
happen under normal conditions, consider adjusting the
|
||||
:option:`--max-connection-attempts` option.
|
||||
|
||||
There have been observations that services, that in their nature offer
|
||||
promiscious IP addresses, such as cloud, VPN and public proxy services, might be
|
||||
promiscuous IP addresses, such as cloud, VPN and public proxy services, might be
|
||||
subject to significantly stricter limits for anonymous access. However,
|
||||
logged-in accesses (see :option:`--login`) do not seem to be affected.
|
||||
|
||||
@@ -46,7 +46,7 @@ Private but not followed
|
||||
You have to follow a private account to access most of its associated
|
||||
information.
|
||||
|
||||
Login Error
|
||||
Login error
|
||||
-----------
|
||||
|
||||
Instaloader's login *should* work fine, both with and without
|
||||
@@ -55,13 +55,13 @@ issued when Instagram suspects authentication activity on your account, by
|
||||
pointing the user to an URL to be opened in a browser.
|
||||
|
||||
Nevertheless, in :issue:`92` and :issue:`615` users reported problems with
|
||||
logging in. We recommend to always keep the sessionfile which Instaloader
|
||||
creates when using :option:`--login`. If a sessionfile is present,
|
||||
logging in. We recommend to always keep the session file which Instaloader
|
||||
creates when using :option:`--login`. If a session file is present,
|
||||
:option:`--login` does not make make use of the failure-prone login procedure.
|
||||
Also, sessionfiles usually do not expire and can be copied between different
|
||||
Also, session files usually do not expire and can be copied between different
|
||||
computers without any problems.
|
||||
|
||||
If you do not have a sessionfile present, you may use the following script
|
||||
If you do not have a session file present, you may use the following script
|
||||
(:example:`615_import_firefox_session.py`) to workaround login problems by
|
||||
importing the session cookies from Firefox and bypassing Instaloader's login and
|
||||
so still use Instaloader's logged-in functionality.
|
||||
@@ -78,7 +78,7 @@ To use this script,
|
||||
|
||||
#. Then, ``instaloader -l USERNAME`` should work fine.
|
||||
|
||||
This script also supports specifying a cookiefile path, which may be useful if
|
||||
This script also supports specifying a cookie file path, which may be useful if
|
||||
you use multiple Firefox profiles or if your operating system has the directory
|
||||
structure differently set up. Also, you can specify an alternative sessionfile
|
||||
structure differently set up. Also, you can specify an alternative session file
|
||||
path.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||
|
||||
|
||||
__version__ = '4.5a1'
|
||||
__version__ = '4.5.1'
|
||||
|
||||
|
||||
try:
|
||||
|
||||
+40
-35
@@ -13,7 +13,7 @@ from functools import wraps
|
||||
from hashlib import md5
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Iterator, List, Optional, Set, Union
|
||||
from typing import Any, Callable, IO, Iterator, List, Optional, Set, Union, cast
|
||||
|
||||
import requests
|
||||
import urllib3 # type: ignore
|
||||
@@ -351,7 +351,8 @@ class Instaloader:
|
||||
except UnicodeEncodeError:
|
||||
self.context.log('txt', end=' ', flush=True)
|
||||
with open(filename, 'wb') as text_file:
|
||||
shutil.copyfileobj(BytesIO(bcaption), text_file)
|
||||
with BytesIO(bcaption) as bio:
|
||||
shutil.copyfileobj(cast(IO, bio), text_file)
|
||||
os.utime(filename, (datetime.now().timestamp(), mtime.timestamp()))
|
||||
|
||||
def save_location(self, filename: str, location: PostLocation, mtime: datetime) -> None:
|
||||
@@ -361,7 +362,8 @@ class Instaloader:
|
||||
"https://maps.google.com/maps?q={0},{1}&ll={0},{1}\n".format(location.lat,
|
||||
location.lng))
|
||||
with open(filename, 'wb') as text_file:
|
||||
shutil.copyfileobj(BytesIO(location_string.encode()), text_file)
|
||||
with BytesIO(location_string.encode()) as bio:
|
||||
shutil.copyfileobj(cast(IO, bio), text_file)
|
||||
os.utime(filename, (datetime.now().timestamp(), mtime.timestamp()))
|
||||
self.context.log('geo', end=' ', flush=True)
|
||||
|
||||
@@ -511,26 +513,27 @@ class Instaloader:
|
||||
|
||||
# Download the image(s) / video thumbnail and videos within sidecars if desired
|
||||
downloaded = True
|
||||
if self.download_pictures:
|
||||
if post.typename == 'GraphSidecar':
|
||||
edge_number = 1
|
||||
for sidecar_node in post.get_sidecar_nodes():
|
||||
# Download picture or video thumbnail
|
||||
if not sidecar_node.is_video or self.download_video_thumbnails is True:
|
||||
downloaded &= self.download_pic(filename=filename, url=sidecar_node.display_url,
|
||||
mtime=post.date_local, filename_suffix=str(edge_number))
|
||||
# Additionally download video if available and desired
|
||||
if sidecar_node.is_video and self.download_videos is True:
|
||||
downloaded &= self.download_pic(filename=filename, url=sidecar_node.video_url,
|
||||
mtime=post.date_local, filename_suffix=str(edge_number))
|
||||
edge_number += 1
|
||||
elif post.typename == 'GraphImage':
|
||||
if post.typename == 'GraphSidecar':
|
||||
for edge_number, sidecar_node in enumerate(post.get_sidecar_nodes(), start=1):
|
||||
if self.download_pictures and (not sidecar_node.is_video or self.download_video_thumbnails):
|
||||
# Download sidecar picture or video thumbnail (--no-pictures implies --no-video-thumbnails)
|
||||
downloaded &= self.download_pic(filename=filename, url=sidecar_node.display_url,
|
||||
mtime=post.date_local, filename_suffix=str(edge_number))
|
||||
if sidecar_node.is_video and self.download_videos:
|
||||
# Download sidecar video if desired
|
||||
downloaded &= self.download_pic(filename=filename, url=sidecar_node.video_url,
|
||||
mtime=post.date_local, filename_suffix=str(edge_number))
|
||||
elif post.typename == 'GraphImage':
|
||||
# Download picture
|
||||
if self.download_pictures:
|
||||
downloaded = self.download_pic(filename=filename, url=post.url, mtime=post.date_local)
|
||||
elif post.typename == 'GraphVideo':
|
||||
if self.download_video_thumbnails is True:
|
||||
elif post.typename == 'GraphVideo':
|
||||
# Download video thumbnail (--no-pictures implies --no-video-thumbnails)
|
||||
if self.download_pictures and self.download_video_thumbnails:
|
||||
with self.context.error_catcher("Video thumbnail of {}".format(post)):
|
||||
downloaded = self.download_pic(filename=filename, url=post.url, mtime=post.date_local)
|
||||
else:
|
||||
self.context.error("Warning: {0} has unknown typename: {1}".format(post, post.typename))
|
||||
else:
|
||||
self.context.error("Warning: {0} has unknown typename: {1}".format(post, post.typename))
|
||||
|
||||
# Save caption if desired
|
||||
metadata_string = _ArbitraryItemFormatter(post).format(self.post_metadata_txt_pattern).strip()
|
||||
@@ -538,7 +541,7 @@ class Instaloader:
|
||||
self.save_caption(filename=filename, mtime=post.date_local, caption=metadata_string)
|
||||
|
||||
# Download video if desired
|
||||
if post.is_video and self.download_videos is True:
|
||||
if post.is_video and self.download_videos:
|
||||
downloaded &= self.download_pic(filename=filename, url=post.video_url, mtime=post.date_local)
|
||||
|
||||
# Download geotags if desired
|
||||
@@ -546,11 +549,11 @@ class Instaloader:
|
||||
self.save_location(filename, post.location, post.date_local)
|
||||
|
||||
# Update comments if desired
|
||||
if self.download_comments is True:
|
||||
if self.download_comments:
|
||||
self.update_comments(filename=filename, post=post)
|
||||
|
||||
# Save metadata as JSON if desired.
|
||||
if self.save_metadata is not False:
|
||||
if self.save_metadata:
|
||||
self.save_metadata_json(filename, post)
|
||||
|
||||
self.context.log()
|
||||
@@ -728,9 +731,9 @@ class Instaloader:
|
||||
"""
|
||||
Download the Posts returned by given Post Iterator.
|
||||
|
||||
..versionadded:: 4.4
|
||||
.. versionadded:: 4.4
|
||||
|
||||
..versionchanged:: 4.5
|
||||
.. versionchanged:: 4.5
|
||||
Transparently resume an aborted operation if `posts` is a :class:`NodeIterator`.
|
||||
|
||||
:param posts: Post Iterator to loop through.
|
||||
@@ -743,27 +746,29 @@ class Instaloader:
|
||||
"""
|
||||
displayed_count = (max_count if total_count is None or max_count is not None and max_count < total_count
|
||||
else total_count)
|
||||
sanitized_target = target
|
||||
if isinstance(target, str):
|
||||
sanitized_target = _PostPathFormatter.sanitize_path(target)
|
||||
with resumable_iteration(
|
||||
context=self.context,
|
||||
iterator=posts,
|
||||
load=load_structure_from_file,
|
||||
save=save_structure_to_file,
|
||||
format_path=lambda magic: self.format_filename_within_target_path(
|
||||
target, owner_profile, self.resume_prefix or '', magic, 'json.xz'
|
||||
sanitized_target, owner_profile, self.resume_prefix or '', magic, 'json.xz'
|
||||
),
|
||||
check_bbd=self.check_resume_bbd,
|
||||
enabled=self.resume_prefix is not None
|
||||
) as resume_info:
|
||||
is_resuming, start_index = resume_info
|
||||
for number, post in enumerate(posts):
|
||||
if max_count is not None and number + start_index >= max_count:
|
||||
) as (is_resuming, start_index):
|
||||
for number, post in enumerate(posts, start=start_index + 1):
|
||||
if max_count is not None and number > max_count:
|
||||
break
|
||||
if displayed_count is not None:
|
||||
self.context.log("[{0:{w}d}/{1:{w}d}] ".format(number + start_index + 1, displayed_count,
|
||||
self.context.log("[{0:{w}d}/{1:{w}d}] ".format(number, displayed_count,
|
||||
w=len(str(displayed_count))),
|
||||
end="", flush=True)
|
||||
else:
|
||||
self.context.log("[{:3d}] ".format(number + start_index + 1), end="", flush=True)
|
||||
self.context.log("[{:3d}] ".format(number), end="", flush=True)
|
||||
if post_filter is not None:
|
||||
try:
|
||||
if not post_filter(post):
|
||||
@@ -1103,11 +1108,11 @@ class Instaloader:
|
||||
def _error_raiser(_str):
|
||||
yield
|
||||
|
||||
# error_handler type is Callable[[Optional[str]], ContextManager[None]] (not supported with Python 3.5)
|
||||
# error_handler type is Callable[[Optional[str]], ContextManager[None]] (not supported with Python 3.5.0..3.5.3)
|
||||
error_handler = _error_raiser if raise_errors else self.context.error_catcher
|
||||
|
||||
for profile in profiles:
|
||||
with error_handler(profile.username): # type: ignore
|
||||
with error_handler(profile.username): # type: ignore # (ignore type for Python 3.5 support)
|
||||
profile_name = profile.username
|
||||
|
||||
# Download profile picture
|
||||
|
||||
@@ -24,7 +24,7 @@ def copy_session(session: requests.Session, request_timeout: Optional[float] = N
|
||||
"""Duplicates a requests.Session."""
|
||||
new = requests.Session()
|
||||
new.cookies = requests.utils.cookiejar_from_dict(requests.utils.dict_from_cookiejar(session.cookies))
|
||||
new.headers = session.headers.copy() # type: ignore
|
||||
new.headers = session.headers.copy()
|
||||
if request_timeout is not None:
|
||||
# Override default timeout behavior.
|
||||
# Need to silence mypy bug for this. See: https://github.com/python/mypy/issues/2427
|
||||
@@ -431,7 +431,7 @@ class InstaloaderContext:
|
||||
"""
|
||||
Retrieve a list of GraphQL nodes.
|
||||
|
||||
..deprecated:: 4.5
|
||||
.. deprecated:: 4.5
|
||||
Use :class:`NodeIterator` instead, which provides more functionality.
|
||||
"""
|
||||
|
||||
@@ -487,7 +487,7 @@ class InstaloaderContext:
|
||||
shutil.copyfileobj(resp.raw, file)
|
||||
else:
|
||||
file.write(resp)
|
||||
os.rename(filename + '.temp', filename)
|
||||
os.replace(filename + '.temp', filename)
|
||||
|
||||
def get_raw(self, url: str, _attempt=1) -> requests.Response:
|
||||
"""Downloads a file anonymously.
|
||||
@@ -559,14 +559,18 @@ class RateController:
|
||||
# pylint:disable=no-self-use
|
||||
time.sleep(secs)
|
||||
|
||||
def _dump_query_timestamps(self, current_time: float):
|
||||
def _dump_query_timestamps(self, current_time: float, failed_query_type: str):
|
||||
windows = [10, 11, 15, 20, 30, 60]
|
||||
print("GraphQL requests:", file=sys.stderr)
|
||||
for query_hash, times in self._graphql_query_timestamps.items():
|
||||
print(" {}".format(query_hash), file=sys.stderr)
|
||||
for window in windows:
|
||||
reqs_in_sliding_window = sum(t > current_time - window * 60 for t in times)
|
||||
print(" last {} minutes: {} requests".format(window, reqs_in_sliding_window), file=sys.stderr)
|
||||
self._context.error("Requests within last {} minutes grouped by type:"
|
||||
.format('/'.join(str(w) for w in windows)),
|
||||
repeat_at_end=False)
|
||||
for query_type, times in self._graphql_query_timestamps.items():
|
||||
reqs_in_sliding_window = [sum(t > current_time - w * 60 for t in times) for w in windows]
|
||||
self._context.error(" {} {:>32}: {}".format(
|
||||
"*" if query_type == failed_query_type else " ",
|
||||
query_type,
|
||||
" ".join("{:4}".format(reqs) for reqs in reqs_in_sliding_window)
|
||||
), repeat_at_end=False)
|
||||
|
||||
def count_per_sliding_window(self, query_type: str) -> int:
|
||||
"""Return how many GraphQL requests can be done within the sliding window."""
|
||||
@@ -610,16 +614,17 @@ class RateController:
|
||||
def handle_429(self, query_type: str) -> None:
|
||||
"""This method is called to handle a 429 Too Many Requests response. It calls :meth:`RateController.sleep` to
|
||||
wait until we can repeat the same request."""
|
||||
text_for_429 = ("HTTP error code 429 was returned because too many queries occurred in the last time. "
|
||||
"Please do not use Instagram in your browser or run multiple instances of Instaloader "
|
||||
"in parallel.")
|
||||
print(textwrap.fill(text_for_429), file=sys.stderr)
|
||||
current_time = time.monotonic()
|
||||
waittime = self.query_waittime(query_type, current_time, True)
|
||||
assert waittime >= 0
|
||||
if waittime > 15:
|
||||
self._context.log("The request will be retried in {} seconds, at {:%H:%M}."
|
||||
.format(round(waittime), datetime.now() + timedelta(seconds=waittime)))
|
||||
self._dump_query_timestamps(current_time)
|
||||
self._dump_query_timestamps(current_time, query_type)
|
||||
text_for_429 = ("Instagram responded with HTTP error \"429 - Too Many Requests\". Please do not run multiple "
|
||||
"instances of Instaloader in parallel or within short sequence. Also, do not use any Instagram "
|
||||
"App while Instaloader is running.")
|
||||
self._context.error(textwrap.fill(text_for_429), repeat_at_end=False)
|
||||
if waittime > 1.5:
|
||||
self._context.error("The request will be retried in {} seconds, at {:%H:%M}."
|
||||
.format(round(waittime), datetime.now() + timedelta(seconds=waittime)),
|
||||
repeat_at_end=False)
|
||||
if waittime > 0:
|
||||
self.sleep(waittime)
|
||||
|
||||
@@ -59,13 +59,13 @@ class NodeIterator(Iterator[T]):
|
||||
been constructed with the same parameters as the instance that is represented by the :class:`FrozenNodeIterator` in
|
||||
question. This is to ensure that an iteration cannot be resumed in a wrong, unmatching loop. As a quick way to
|
||||
distinguish iterators that are saved e.g. in files, there is the :attr:`NodeIterator.magic` string: Two
|
||||
NodeIterators are matching if they have the same magic.
|
||||
NodeIterators are matching if and only if they have the same magic.
|
||||
|
||||
See also :func:`resumable_iteration` for a high-level context manager that handles a resumable iteration.
|
||||
"""
|
||||
|
||||
_graphql_page_length = 50
|
||||
shelf_life = timedelta(days=29)
|
||||
_shelf_life = timedelta(days=29)
|
||||
|
||||
def __init__(self,
|
||||
context: InstaloaderContext,
|
||||
@@ -85,7 +85,7 @@ class NodeIterator(Iterator[T]):
|
||||
self._page_index = 0
|
||||
self._total_index = 0
|
||||
self._best_before = (None if first_data is None else
|
||||
datetime.now() + NodeIterator.shelf_life)
|
||||
datetime.now() + NodeIterator._shelf_life)
|
||||
|
||||
def _query(self, after: Optional[str] = None) -> Dict:
|
||||
pagination_variables = {'first': NodeIterator._graphql_page_length} # type: Dict[str, Any]
|
||||
@@ -97,7 +97,7 @@ class NodeIterator(Iterator[T]):
|
||||
self._query_hash, {**self._query_variables, **pagination_variables}, self._query_referer
|
||||
)
|
||||
)
|
||||
self._best_before = datetime.now() + NodeIterator.shelf_life
|
||||
self._best_before = datetime.now() + NodeIterator._shelf_life
|
||||
return data
|
||||
except QueryReturnedBadRequestException:
|
||||
new_page_length = int(NodeIterator._graphql_page_length / 2)
|
||||
@@ -112,7 +112,7 @@ class NodeIterator(Iterator[T]):
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
def __next__(self) -> T:
|
||||
if self._data is None:
|
||||
self._data = self._query()
|
||||
if self._page_index < len(self._data['edges']):
|
||||
@@ -177,7 +177,15 @@ class NodeIterator(Iterator[T]):
|
||||
)
|
||||
|
||||
def thaw(self, frozen: FrozenNodeIterator) -> None:
|
||||
"""Use this iterator for resuming from earlier iteration."""
|
||||
"""
|
||||
Use this iterator for resuming from earlier iteration.
|
||||
|
||||
:raises InvalidArgumentException:
|
||||
If
|
||||
|
||||
- the iterator on which this method is called has already been used, or
|
||||
- the given :class:`FrozenNodeIterator` does not match, i.e. belongs to a different iteration.
|
||||
"""
|
||||
if self._total_index or self._page_index:
|
||||
raise InvalidArgumentException("thaw() called on already-used iterator.")
|
||||
if (self._query_hash != frozen.query_hash or
|
||||
@@ -211,8 +219,7 @@ def resumable_iteration(context: InstaloaderContext,
|
||||
load=lambda _, path: FrozenNodeIterator(**json.load(open(path))),
|
||||
save=lambda fni, path: json.dump(fni._asdict(), open(path, 'w')),
|
||||
format_path=lambda magic: "resume_info_{}.json".format(magic)
|
||||
) as resume_info:
|
||||
is_resuming, start_index = resume_info
|
||||
) as (is_resuming, start_index):
|
||||
for post in post_iterator:
|
||||
do_something_with(post)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ PostCommentAnswer.likes_count.__doc__ = "Number of likes on comment."
|
||||
|
||||
PostComment = namedtuple('PostComment', (*PostCommentAnswer._fields, 'answers')) # type: ignore
|
||||
for field in PostCommentAnswer._fields:
|
||||
getattr(PostComment, field).__doc__ = getattr(PostCommentAnswer, field).__doc__
|
||||
getattr(PostComment, field).__doc__ = getattr(PostCommentAnswer, field).__doc__ # pylint: disable=no-member
|
||||
PostComment.answers.__doc__ = r"Iterator which yields all :class:`PostCommentAnswer`\ s for the comment." # type: ignore
|
||||
|
||||
PostLocation = namedtuple('PostLocation', ['id', 'name', 'slug', 'has_public_page', 'lat', 'lng'])
|
||||
@@ -782,7 +782,7 @@ class Profile:
|
||||
def get_posts(self) -> NodeIterator[Post]:
|
||||
"""Retrieve all posts from a profile.
|
||||
|
||||
:rtype:NodeIterator[Post]"""
|
||||
:rtype: NodeIterator[Post]"""
|
||||
self._obtain_metadata()
|
||||
return NodeIterator(
|
||||
self._context,
|
||||
@@ -797,7 +797,7 @@ class Profile:
|
||||
def get_saved_posts(self) -> NodeIterator[Post]:
|
||||
"""Get Posts that are marked as saved by the user.
|
||||
|
||||
:rtype:NodeIterator[Post]"""
|
||||
:rtype: NodeIterator[Post]"""
|
||||
|
||||
if self.username != self._context.username:
|
||||
raise LoginRequiredException("--login={} required to get that profile's saved posts.".format(self.username))
|
||||
@@ -851,7 +851,7 @@ class Profile:
|
||||
Retrieve list of followers of given profile.
|
||||
To use this, one needs to be logged in and private profiles has to be followed.
|
||||
|
||||
:rtype:NodeIterator[Profile]
|
||||
:rtype: NodeIterator[Profile]
|
||||
"""
|
||||
if not self._context.is_logged_in:
|
||||
raise LoginRequiredException("--login required to get a profile's followers.")
|
||||
@@ -870,7 +870,7 @@ class Profile:
|
||||
Retrieve list of followees (followings) of given profile.
|
||||
To use this, one needs to be logged in and private profiles has to be followed.
|
||||
|
||||
:rtype:NodeIterator[Profile]
|
||||
:rtype: NodeIterator[Profile]
|
||||
"""
|
||||
if not self._context.is_logged_in:
|
||||
raise LoginRequiredException("--login required to get a profile's followees.")
|
||||
@@ -1334,7 +1334,7 @@ class Hashtag:
|
||||
yield from (Post(self._context, edge["node"]) for edge in conn["edges"])
|
||||
|
||||
def get_all_posts(self) -> Iterator[Post]:
|
||||
"""Yields all posts, i.e. all most recent posts and the top posts, in chronological order."""
|
||||
"""Yields all posts, i.e. all most recent posts and the top posts, in almost-chronological order."""
|
||||
sorted_top_posts = iter(sorted(self.get_top_posts(), key=lambda p: p.date_utc, reverse=True))
|
||||
other_posts = self.get_posts()
|
||||
next_top = next(sorted_top_posts, None)
|
||||
|
||||
Reference in New Issue
Block a user